A quick demonstration of how different devices can be targeted with the same codebase
Credits:
Boid adapted from a BIONIC SIMULATION (C) 2001 - Luis Pabon (luis@pabon.com) made available on FlashKit.
Original mentions that "You can freely use and modify this code but please, give credit to the author"
- Ported to AS3
- Added logic to support different types of controllers (point and click vs accelerometer)
*
How to use:
- In accelerometer supported environments, move your device around.
- Elsewhere, move your mouse on the blue part of the screen
/**
* Copyright widged ( http://wonderfl.net/user/widged )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/bQJS
*/
/**
* Boid for Android
*
* A quick demonstration of how different devices can be targeted with the same codebase
*
* Credits:
* Boid adapted from a BIONIC SIMULATION (C) 2001 - Luis Pabon (luis@pabon.com) made available on FlashKit.
* Original mentions that "You can freely use and modify this code but please, give credit to the author"
* - Ported to AS3
* - Added logic to support different types of controllers (point and click vs accelerometer)
*
* How to use:
* - In accelerometer supported environments, move your device around.
* - Elsewhere, move your mouse on the blue part of the screen
*/
package
{
import flash.display.Sprite;
import flash.events.EventDispatcher;
import flash.sensors.Accelerometer;
[SWF(width = "480", height = "800", backgroundColor='#FFFFFF')]
public class BoidFishWonderfl extends Sprite
{
private var gameScreen:GameScreen;
private var gameDispatcher:EventDispatcher;
private var gameController:IDeviceController;
public function BoidFishWonderfl()
{
gameDispatcher = new EventDispatcher();
gameController = (Accelerometer.isSupported) ? new AndroidController(gameDispatcher) : new DefaultController(gameDispatcher);
gameScreen = new GameScreen();
gameScreen.gameController = gameController;
addChild(gameScreen);
}
}
}
import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.AccelerometerEvent;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.sensors.Accelerometer;
import flash.system.LoaderContext;
import flash.system.SecurityDomain;
class GameScreen extends Sprite
{
// controllers
private var _controller:IDeviceController;
private var boidFish:Boid;
public function GameScreen()
{
var shape:Shape = new Shape();
shape.graphics.beginFill(0x204A87,0.8);
shape.graphics.drawRect(0,0,480, 762);
addChild(shape);
}
public function set gameController(value:IDeviceController):void
{
_controller = value;
_controller.init(this);
}
public function addBoid(boid:Boid):void
{
boidFish = boid;
addChild(boid);
}
public function updateTarget(pt:Point):void
{
var screenRect:Rectangle = _controller.screenRectangle;
if(pt.x < screenRect.left) { pt.x = screenRect.left; } else if(pt.x > screenRect.right) { pt.x = screenRect.right; }
if(pt.y < screenRect.top) { pt.y = screenRect.top; } else if(pt.y > screenRect.bottom) { pt.y = screenRect.bottom; }
boidFish.upateTarget(pt);
}
}
// ########################
// # Boids
// ########################
class Boid extends Sprite
{
private var nodeList:Array = [];
private var firstNode:BoidFirstNode
public function Boid(spriteList:Array)
{
super();
init(spriteList);
}
/**
* initialisation
*/
private function init(spriteList:Array):void
{
var i:int;
nodeList = [];
var node:BoidNode;
var previousNode:BoidNode;
// We built the creature:
previousNode = null;
for ( i = 0; i < spriteList.length; i++) {
var sprite:Sprite = spriteList[i] as Sprite;
if(i == 0)
node = firstNode = new BoidFirstNode(sprite);
else
node = new BoidNode(sprite, previousNode);
nodeList.push(node);
previousNode = node;
this.addChild(sprite);
}
}
public function upateTarget(pt:Point):void
{
// Moves the head towards the mouse cursor:
firstNode.targetPosition = pt;
for(var i:int = 0; i < nodeList.length; i++)
{
(nodeList[i] as BoidNode).refresh();
}
}
public function hitTestPick(pick:DisplayObject):Boolean
{
return (nodeList[1] as BoidNode).hitTestObject(pick);
}
}
class BoidNode
{
// parameters
private var _scaleFactor:Number = 0.96;
private var _alphaFactor:Number = 0.95;
private var _compactFactor:Number = 2; // = compactness (depends on R)
// class variables
protected var _sprite:Sprite;
protected var _targetPosition:Point = new Point(0,0);
protected var _scale:Number = 1;
protected var _alpha:Number = 1;
private var _previousNode:BoidNode;
public function BoidNode(sprite:Sprite, previousNode:BoidNode)
{
_sprite = sprite;
_previousNode = previousNode;
}
public function refresh():void
{
if(!_sprite) { return; }
if(_previousNode) {
// We reduce size a bit, and reduce the opacity of the tail:
_scale = _previousNode.scaleFactor * _scaleFactor;
_alpha = _previousNode.alphaFactor * _alphaFactor;
}
_sprite.scaleX = _sprite.scaleY = _scale;
_sprite.alpha = _alpha;
if(_previousNode) {
// compute target
_targetPosition.x += (_previousNode.targetPosition.x - _targetPosition.x) / _compactFactor;
_targetPosition.y += (_previousNode.targetPosition.y - _targetPosition.y) / _compactFactor;
// updateposition
var targetLoc:Point = _previousNode.targetPosition;
}
_sprite.x = (targetLoc.x + _targetPosition.x) / 2;
_sprite.y = (targetLoc.y + _targetPosition.y) / 2;;
// And we calculate the right orientation of each piece:
_sprite.rotation = 57.295778*Math.atan2((_targetPosition.y-targetLoc.y),(_targetPosition.x-targetLoc.x));
}
public function get scaleFactor():Number { return _scale; }
public function get alphaFactor():Number { return _alpha; }
public function get targetPosition():Point { return _targetPosition; }
public function hitTestObject(object:DisplayObject):Boolean
{
if(!_sprite) { return false;}
return _sprite.hitTestObject(object);
}
}
class BoidFirstNode extends BoidNode
{
private var R:Number = 10; // = how slow the fish follows the mouse for firstNode only
private var _previousPosition:Point;
public function BoidFirstNode(sprite:Sprite)
{
super(sprite, null);
}
public function set targetPosition(pt:Point):void
{
_previousPosition = _targetPosition
var posX:Number = Math.round(_previousPosition.x + (pt.x - _previousPosition.x ) / R);
var posY:Number = Math.round(_previousPosition.y + (pt.y - _previousPosition.y ) / R);
_targetPosition = new Point(posX, posY);
refresh();
}
override public function refresh():void
{
if(!_sprite) { return; }
_sprite.scaleX = _sprite.scaleY = _scale;
_sprite.alpha = _alpha;
_sprite.x = (_previousPosition.x + _targetPosition.x) / 2;
_sprite.y = (_previousPosition.y + _targetPosition.y) / 2;;
// And we calculate the right orientation of each piece:
_sprite.rotation = 57.295778*Math.atan2((_previousPosition.y- _targetPosition.y),(_previousPosition.x-_targetPosition.x));
}
}
class BoidUtils
{
/**
* Construct the list of images
*/
public static function makeSpriteList(nodeQty:int, DefaultImageClass:Class, extraImages:Array):Array
{
var arr:Array = [];
var sprite:Sprite;
for (var i:int = 0; i < nodeQty ; i++ )
{
sprite = new DefaultImageClass();
arr[i] = sprite;
}
for each(var part:Object in extraImages)
{
var posList:Array = part.positions;
for (var j:int = 0; j < posList.length; j++)
{
var idx:int = posList[j];
var ImgClass:Class = part.image as Class;
sprite = new ImgClass();
arr[idx] = sprite;
}
}
return arr;
}
}
// ########################
// # Devices Controllers
// #
// # Provide different behaviors depending on the device the game runs on
// # Touch if accelerometer is supported, point and click otherwise
// ########################
interface IDeviceController
{
function get dispatcher():IEventDispatcher;
function get screenRectangle():Rectangle;
function init(gameScreen:GameScreen):void;
}
class GameController implements IDeviceController
{
protected var eventTarget:IEventDispatcher;
protected var gameScreen:GameScreen;
protected var screenRect:Rectangle;
// status
private var gameStatus:int = STATUS_LOADING;
private const STATUS_LOADING:int = 0;
private const STATUS_STARTING:int = 1;
private const STATUS_PLAYING:int = 2;
private const STATUS_PAUSED:int = 3;
private const STATUS_END:int = 4;
// images to be used as sprite
private var Head:Class;
private var Spine:Class;
private var Fin:Class;
public function GameController(target:IEventDispatcher)
{
eventTarget = target;
}
public function get screenRectangle():Rectangle { return screenRect; }
public function get dispatcher():IEventDispatcher { return eventTarget; }
// :TODO: could be more decoupled by getting the gamescreen reference out of here
// and have everything go by way of events.
public function init(screen:GameScreen):void
{
gameScreen = screen;
loadAssets();
}
protected function deviceInit():void
{
throw new Error("must be overloaded by inheriting classes")
}
private function changeStatus(id:int):void
{
gameStatus = id;
}
private function loadAssets():void
{
gameStatus = STATUS_LOADING;
var loader:Loader = new Loader();
loader.load(new URLRequest("http://widged.com/labs/wonderfl/bionicFish.swf"), new LoaderContext(true, null, SecurityDomain.currentDomain));
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete)
}
private function onLoadComplete(event:Event):void
{
var loaderInfo:LoaderInfo = LoaderInfo(event.target);
Head = Class(loaderInfo.applicationDomain.getDefinition("Head"));
Spine = Class(loaderInfo.applicationDomain.getDefinition("Spine"));
Fin = Class(loaderInfo.applicationDomain.getDefinition("Fin"));
start();
}
private function start():void
{
gameStatus = STATUS_STARTING;
var spriteList:Array = BoidUtils.makeSpriteList(20, Spine, [{image: Head, positions: [0]},{image: Fin, positions: [3, 13]}]);
var boid:Boid = new Boid(spriteList);
gameScreen.addBoid(boid);
// pickList = PickableUtils.generateSpriteList(5);
// PickableUtils.addOntoDisplayObject(gameContainer, pickList, screenRect);
deviceInit();
gameStatus = STATUS_PLAYING;
}
protected function updateTarget(pt:Point):void
{
gameScreen.updateTarget(pt);
}
private function testAssets():void
{
// test
var head:DisplayObject = DisplayObject(new Head());
gameScreen.addChild(head);
var spine:DisplayObject = DisplayObject(new Spine());
spine.x = 40;
gameScreen.addChild(spine);
var fin:DisplayObject = DisplayObject(new Fin());
fin.x = 80;
gameScreen.addChild(fin);
}
}
class AndroidController extends GameController implements IDeviceController
{
private var boidTarget:Point = new Point(200,200);
private var accelerometer:Accelerometer;
private var sensitivity:int = 20;
public function AndroidController(target:IEventDispatcher)
{
super(target);
screenRect = new Rectangle(0, 0, 480, 762);
}
override protected function deviceInit():void
{
accelerometer = new Accelerometer();
accelerometer.addEventListener(AccelerometerEvent.UPDATE, onAccelerometerChange);
accelerometer.setRequestedUpdateInterval(40);
}
// #### Accelerometer Movement
public function onAccelerometerChange(event:AccelerometerEvent):void {
var ax:Number = (event.accelerationX*sensitivity)*-1;
var ay:Number = (event.accelerationY*sensitivity);
var az:Number = event.accelerationZ;
var tx:Number = boidTarget.x + ax;
var ty:Number = boidTarget.y + ay;
boidTarget = new Point(tx, ty);
super.updateTarget(boidTarget)
}
}
class DefaultController extends GameController implements IDeviceController
{
public function DefaultController(target:IEventDispatcher)
{
super(target);
screenRect = new Rectangle(0, 0, 480, 762);
}
override protected function deviceInit():void
{
gameScreen.addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
var pt:Point = new Point(gameScreen.mouseX, gameScreen.mouseY);
super.updateTarget(pt)
}
}