Biker's Heaven

Comments

I am always looking for ways to enrich the interactions between user and computer. In studying mathematics, physics, and economics, students are asked to graph functions. This is a complex mental activity, integrating many pieces of information into a global picture.

In on-line learning, however, graphing is hard to implement. Most often, students are shown several graphs and asked to choose the correct one. This is a poor substitute for actually creating the graph yourself. Outside of school, we don't live in a multiple-choice world. The question is, how can a program "read" a student's graph, and "understand" it sufficiently to provide useful feedback?

This Flash movie illustrates one approach to interacting with a user-drawn graph. A different approach is used in the still-unfinished rational-function sketching tool.

This biker has also been integrated into a birthday card. Everything in that card is controlled by ActionScript, with the exception of the biker's feet pedaling. The only part that requires hand-work is filling in some of the spaces between letters with invisible content, to keep the biker from falling into the cracks.

Code


  /*************************************************************************\
  Actionscript 1.0 code for aligning a "bike" movieclip with the visible
  content of a "ground" movieclip, including dynamically drawn content.
  
  © 2004 Michael Kantor.  
  
  You are free to re-use this code for any purpose, or
  develop other code based on it.  Acknowledgement, and a link 
  to flashgizmo.com, are appreciated but not required.
  \**************************************************************************/



var mousing;  // boolean.  true when using mouse
ground.swapDepths(10);
var st = 10;  // step size (speed)
var deg = Math.PI / 180;

// we need the sine and cosine of 5 degrees, so save them here
var s5 = Math.sin(5 * Math.PI / 180);
var c5 = Math.cos(5 * Math.PI / 180);

rideOnLine = function() {
    var y, nx, n;
    var k = Key.getCode();
    if((k != Key.LEFT) && (k != Key.RIGHT /*&& k != Key.UP*/) && !mousing) return;		

    var isKey = Key.isDown(Key.LEFT) || Key.isDown(Key.RIGHT);
    if (isKey) {  // if keys are down, direction is determined by which key
	if (Key.isDown(Key.LEFT)) { 
	    this._xscale = -100; 	// bike points to the left
	    s5 = -Math.abs(s5);	// change rotation for sensing
	}
	if (Key.isDown(Key.RIGHT)) { 
	    this._xscale = 100; 
	    s5 = Math.abs(s5);
	}		
	var k = Key.getCode();

	// move by step amount st in direction bike is pointing
	if (k == Key.RIGHT) {
	    this._x += st * Math.cos(this._rotation * deg);
	    this._y += st * Math.sin(this._rotation * deg);
	}
	else if (k == Key.LEFT) {
	    this._x -= st * Math.cos(this._rotation * deg);
	    this._y -= st * Math.sin(this._rotation * deg);
	}

	// set up starting location for sensing the ground
	y = this._y - 50;
	x = this._x;

    } else {   // we're draggin with the mouse

	if ((this._x > _root._xmouse && this._xscale > 0) || (this._x < _root._xmouse && this._xscale < 0)) {  
		// find direction by sign of x difference with mouse
	    this._xscale *= -1;		// point in the right direction
	    s5 *= -1;				// set sensing rotation
	}

	// set up starting location for sensing the ground
	x = _root._xmouse;  
	y = _root._ymouse;

	//if(x != this._x) this.play(); else this.stop();
	if(x > Stage.width - 5) x = Stage.width - 5;
	if(x < 5) x = 5;
	this._x = x;

	
	  /****************************************************************\
	  
	  Ground-detection process, part I.  From the starting (x, y), we 	
	  increment y until we either hit the ground or leave the stage.  
	  This gives the point where the rear wheel hits the ground.
	  
	  \******************************************************************/
	

	while(!ground.hitTest(this._x , ++y, true) && y < Stage.height);
	this._y = y;

	if (y == Stage.height) {  // on window edge
	    x = this._x + 30 * this._xscale / 100;
	    this._rotation = 0;
	} else {  // not on bottom window edge, so need to find tilt


	    
	      /****************************************************************\
	      
	      Ground-detection process, part II.   We move 30 pixels straight up 
	      from the rear wheel's contact point.  Then we sweep out an arc of
	      radius 30, moving down and toward the front of the bike, until we
	      hit the ground.  That determines where the front wheel goes. 
	      Basically, we pop a wheelie and then come down.   The rotation 
	      process is optimized by using the trigonometric identities
	      sin(x + y) = sin(x) * cos(y) + cos(x) * sin(y)
	      cos(x + y) = cos(x) * cos(y) - sin(x) * sin(y)
	      Here, "x" is the current rotation, and "y" is 5 degrees.
	      By this dodge, we avoid approximately 36 trig function evaluations
	      per frame.
	      
	      \******************************************************************/
	    


	    y = -30;
	    x = 0;
	    n = -90;   // start at angle of -90 degrees (straight up)
	    var nx;

	    while(!ground.hitTest(this._x + ( nx = x * c5 - y * s5), this._y + (y = y * c5 + x * s5), true) && n < 270) {
		x = nx;
		n += 5;
	    }
	}
    } // end of Mouse-driven option


    if(isKey) { // key-driven motion
	// set position "up" in rider's frame of reference
	var dy = Math.cos(this._rotation * Math.PI / 180);
	var dx = -Math.sin(this._rotation * Math.PI / 180);

	// move 10 pixels "up" in rider's frame of reference
	y = this._y - 10 * dy ;  
	x = this._x - 10 * dx;

	// move "down" in rider's frame until we hit ground
	while(!ground.hitTest(x += dx, y += dy, true) && x > 0 && x < Stage.width && y > 0 && y < Stage.height);
	this._x = x;
	this._y = y;

	// again, set position "up" in rider's frame
	y = -30 * Math.cos((this._rotation - 5) * deg);  // this 30 is the wheelbase!
	x = 30 * Math.sin((this._rotation - 5) * deg);		
	n = this._rotation - 95;
	// now rotate in 5-degree increments until front wheel hits the ground
	var incr = 5 * this._xscale / 100;
	var limit = 72;
	var c = 0;
	while(!ground.hitTest(this._x + ( nx = x * c5 - y * s5), this._y + (y = y * c5 + x * s5), true) && c ++ < limit) {
	    x = nx;
	    n += incr;
	}
	if (this._xscale < 0) n = 180 - n;

    }  // end of key option

    this._rotation = (s5 < 0 ? -n : n);   // a bit of a kludge

    // if we're on the Stage edge, align appropriately
    if(this._y >= Stage.height) this._rotation = 0;
    if(this._y <= 0) this._rotation = 180;
    if(this._x >= Stage.width) this._rotation = -90;
    if(this._x <= 0) this._rotation = 90;

    updateAfterEvent();		// only relevant for mouse motion
}


// make the bike respond to mouse drags and key presses
bike.onPress = function() {
    mousing = true;
    this.onMouseMove = rideOnLine;
}

bike.onRelease = bike.onReleaseOutside = function() {
    delete this.onMouseMove;
}

bike.onKeyDown = function() {
    mousing = false;
    this.onEnterFrame = rideOnLine;
}

Key.addListener(bike);

bike.onKeyUp = function() {
    this.onEnterFrame = null;
}


// make the pencil draw in movie clip with the special name "ground,"
// that is used in the function rideOnLine()

pencil.onPress = function() {
    _root.createEmptyMovieClip("ground", 10);	// wipes out previous drawing
    ground.lineStyle(4, 0x880044);
    ground.moveTo(this._x = _xmouse, this._y = _ymouse);
    this.onMouseMove = function() {
	ground.lineTo(this._x = _xmouse, this._y = _ymouse);
    }
    Mouse.hide();
}
pencil.onRelease = pencil.onReleaseOutside = function() {
    delete this.onMouseMove;
    Mouse.show();
}


You can download the zipped .fla here.
© Michael J. Kantor 3/2004 FlashGizmo.com