Back to Entry Page

Virtual Fishtank Demo 1

Here's Fishtank 1.0, the first of several demonstrations I will offer to help you think about the third assignment (Virtual Fishtank, or Simulation). This demo meets all terms of the assignment except one: for the sake of simplicity I've only built two sprites (fish). You need to have at least six.

The sprites do move as specified, crossing from one side of the screen to the other and reappearing at the opposite edge when they go offstage. They also interact in a way that is at least noticeable if not exactly "interesting."

Obviously there is room for you to improve on this beginning. For one thing, you might simulate something other than a fish tank. Or if you like aquaria, you might try to have your fish move in something other than straight lines. Needless to say, the current interaction (one appears to swallow the other) could be vastly improved upon. And last but not least, watch the example for a few minutes and notice what happens. Since each fish increases in size whenever it swallows its neighbor, and since there's no limit to a fish's size, we soon reach a point of visual absurdity.

I've deliberately left these loose ends in place to give you some thinking room. A more sophisticated version of the demo (Fishtank 1.4) appears at the bottom of this page. Meanwhile here's an anatomy of the simpler version, Fishtank 1.0.

Architecture

The movie has three layers, one for each of the fish and a third for a frame script (more about that later). There is only a single keyframe in each of these layers. There are no multi-frame sequences anywhere in the project.

Animation scripting

Each of the fish is a Movie Clip. Attached to each clip is an enterFrame handler. For the darker fish (the one that always moves from left to right), the script looks in part like this:

onClipEvent(enterFrame){

	//horizontal animation
	this._x +=8;
	//reset if offstage
	if(this._x >600){
		this._x = 0-Math.round(Math.random()*200);
		this._y = Math.round(Math.random()*300);
		}
}

The statements preceded by "//" are comments that label parts of the code so we don't forget what they do. As you can see, most of the work is done by the single line:

this._x +=8

which moves the MovieClip 8 pixels to the right.

Following this statement is an IF condition that limits rightward travel to pixel 600 of the screen. (The movie is built to a 600x400 stage size; it's displayed on this page at 400x200; the Flash player converts all the coordinate values automatically.)

When the fish sprite passes the limit (the right edge of the visible stage), its _x and _y properties (horizontal and vertical position) are reset. Horizontal placement is determined by a random number between 0 (left edge of the screen) and -200 (200 pixels to the left of that edge). Each time the fish resets it pops up at a slightly different offstage location. Thus it takes either more or less time than it did previously to appear on stage.

The _y property controls vertical placement. The value assigned here is a random number between 0 and 300, placing the fish somewhere between the top and bottom edge of the stage.

Because the instructions you've just seen are placed in an enterFrame handler, and because there is only a single frame in the movie, they are executed 12 times per second at 12 fps, creating the appearance of relatively smooth motion.

The scripts for the other fish, which moves right to left, have the same structure but use different numbers for limits and random placement.

Interaction scripting

The code just discussed is only an excerpt of the complete script attached to each fish. Here's what's in the rest of the enterFrame handler for the darker fish:

	if(this.hitTest(_root.fish2) && _root.bumpus == false){
		_root.bumpus = true;
		this._height +=50;
		this._width +=50;
		_root.fish2._visible = false;
	}

As you can see, this IF structure has two conditions. The first is an expression or method called hitTest:

this.hitTest(_root.fish2)

This method determines if two Movie Clips are in contact. It's attached by dot notation to the first Movie Clip, which here is indicated as "this," i.e., the Movie Clip to which this script belongs. The second Movie Clip is invoked by name--"fish2." We use the prefix "_root." because it's required whenever you refer to a Movie Clip not contained within the present Movie Clip. I gave "fish2" its name by setting the instanceName property of that Movie Clip from within the Flash editing environment. (The first fish Movie Clip is named "fish1" in the same way, and fish2's script refers to it as "_root.fish1".)

If the rectangle that contains the current fish is in contact with the rectangle that contains the second fish, then the first part of this double condition is true.

There's a second condition also. A variable named _root.bumpus, an arbitrarily named global variable, must have the binary value false. Why is this here?

We need _root.bumpus in order to make the interaction between the fish a fair fight. Both fish Movie Clips have the script that allows one to "swallow" the other. Therefore when they intersect, both will satisfy the first condition (hitTest). So each will swallow the other and both fish will disappear.

Adding _root.bumpus, which is technically known as a flag or trigger variable, allows us to restrict the victory to the fish who makes first contact. The first Movie Clip that executes its "swallowing" instructions changes the value of _root.bumpus from false to true. Thus when the second fish tries to execute it finds that it can't satisfy the second condition. So the code from fish1 causes fish2 to vanish (by setting its _visible property to false) but the corresponding code from fish2 doesn't execute--unless fish2 was the first to make contact, in which case the situation is reversed.

The winning fish also increases its height and width by 50 pixels, leading to the visual problem we discussed at the outset.

Now, if you just typed in the code as we've explained it you'd notice something significantly wrong. The fish that disappeared on the first collision would never reappear. Also (though you wouldn't be able to observe this directly), since the trigger variable has been flipped to its disabling state the fish would not be capable of interacting a second time.

These problems are resolved by adding two lines to the earlier IF condition, not the one we've just been discussing but the one that resets the position of the fish when it goes offstage. The lines are:

	_root.bumpus = false;
	this._visible = true;

Thus when a fish is apparently "swallowed" by its neighbor it actually just turns invisible and continues to the edge of the stage. When it gets there it assumes a new position, turns visible again, and heads back in for a rematch, which can happen now that the trigger variable is once again set to false.

There's only one more loose end left to explain. The main movie contains a third layer containing a single keyframe which is scripted with the following instruction:

_root.bumpus = false;

Without this statement the first interaction between the fish can't take place because _root.bumpus must be false to allow it, and the variable doesn't get this value until it's assigned. (There are several ways around this problem but I'm leaving some rough edges here the better to show you how the system works.)

With a little experimentation you should be able to adapt these scripts to do a number of things differently. You might vary the fish speed, for instance, or redesign what happens when they collide. You might make them drift in the vertical axis while they swim in the horizontal. And so on.

Below is a somewhat refined version of the initial prototype, fishTank 1.4. If you look at the source file you'll see that it has quite a few differences from fishTank 1.0. I'll go over the features of this improved prototype in a future class.

Source Files

Source files for both versions are available on Crow in MMShare/fishtankDemos.


University of Baltimore Logo

Copyright © 2002 School of Information Arts and Technologies