commit 710ea0e87971230a8183511f6444b931c8975e53 Author: Jeremy Penner Date: Fri Jan 28 17:39:36 2011 -0800 initial commit diff --git a/AIR_readme.txt b/AIR_readme.txt new file mode 100644 index 0000000..0940b4c --- /dev/null +++ b/AIR_readme.txt @@ -0,0 +1,17 @@ + +Instructions for DISTRIBUTING* your application: + +1. Creating a self-signed certificate: +- edit CreateCertificate.bat to change the path to Flex SDK, +- edit CreateCertificate.bat to set your certificate password (and name if you like), +- run CreateCertificate.bat to generate your self-signed certificate, +- wait a minute before packaging. + +2. Packaging the application: +- edit PackageApplication.bat and change the path to Flex SDK, +- if you have a signed certificate, edit PackageApplication.bat to change the path to the certificate, +- run PackageApplication.bat, you will be prompted for the certificate password, + (note that you may not see '***' when typing your password - it works anyway) +- the packaged application should appear in your project in a new 'air' directory. + +* to test your application from FlashDevelop, just press F5 as usual. diff --git a/KlikPunk.as3proj b/KlikPunk.as3proj new file mode 100644 index 0000000..a6e0b0e --- /dev/null +++ b/KlikPunk.as3proj @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + taskkill /f /fi "IMAGENAME eq adl.exe" + + + + + + \ No newline at end of file diff --git a/PackageApplication.bat b/PackageApplication.bat new file mode 100644 index 0000000..3631c22 --- /dev/null +++ b/PackageApplication.bat @@ -0,0 +1,48 @@ +@echo off + +:: AIR application packaging +:: More information: +:: http://livedocs.adobe.com/flex/3/html/help.html?content=CommandLineTools_5.html#1035959 + +:: Path to Flex SDK binaries +set PATH=%PATH%;C:\Dev\flash\flex\bin + +:: Signature (see 'CreateCertificate.bat') +set CERTIFICATE="KlikPunk.pfx" +set SIGNING_OPTIONS=-storetype pkcs12 -keystore %CERTIFICATE% -tsa none +if not exist %CERTIFICATE% goto certificate + +:: Output +if not exist air md air +set AIR_FILE=air/KlikPunk.air + +:: Input +set APP_XML=application.xml +set FILE_OR_DIR=-C bin . + +echo Signing AIR setup using certificate %CERTIFICATE%. +call adt -package %SIGNING_OPTIONS% %AIR_FILE% %APP_XML% %FILE_OR_DIR% +if errorlevel 1 goto failed + +echo. +echo AIR setup created: %AIR_FILE% +echo. +goto end + +:certificate +echo Certificate not found: %CERTIFICATE% +echo. +echo Troubleshotting: +echo A certificate is required, generate one using 'CreateCertificate.bat' +echo. +goto end + +:failed +echo AIR setup creation FAILED. +echo. +echo Troubleshotting: +echo did you configure the Flex SDK path in this Batch file? +echo. + +:end +pause diff --git a/application.xml b/application.xml new file mode 100644 index 0000000..57d59eb --- /dev/null +++ b/application.xml @@ -0,0 +1,27 @@ + + + + KlikPunk + 1.0 + KlikPunk + + KlikPunk + + + + + KlikPunk + KlikPunk.swf + standard + false + true + true + false + false + + + + \ No newline at end of file diff --git a/assets/folder.png b/assets/folder.png new file mode 100644 index 0000000..784e8fa Binary files /dev/null and b/assets/folder.png differ diff --git a/assets/punk.png b/assets/punk.png new file mode 100644 index 0000000..105cba9 Binary files /dev/null and b/assets/punk.png differ diff --git a/assets/readme.txt b/assets/readme.txt new file mode 100644 index 0000000..5550d80 --- /dev/null +++ b/assets/readme.txt @@ -0,0 +1,2 @@ +Icons by Mark James, licensed under a Creative Commons Attribution 2.5 license: +http://www.famfamfam.com/lab/icons/silk/ \ No newline at end of file diff --git a/assets/save.png b/assets/save.png new file mode 100644 index 0000000..99d532e Binary files /dev/null and b/assets/save.png differ diff --git a/src/Button.as b/src/Button.as new file mode 100644 index 0000000..a4dc041 --- /dev/null +++ b/src/Button.as @@ -0,0 +1,21 @@ +package +{ + /** + * ... + * @author jjp + */ + public class Button extends EntitySidebarImg + { + private var dgOnClick:Function; + public function Button(sidebar:Sidebar, bmp:*, dgOnClick: Function) + { + super(sidebar, bmp); + this.dgOnClick = dgOnClick; + } + override public function OnClick():void + { + this.dgOnClick(); + } + } + +} \ No newline at end of file diff --git a/src/Drag.as b/src/Drag.as new file mode 100644 index 0000000..28b2dc6 --- /dev/null +++ b/src/Drag.as @@ -0,0 +1,44 @@ +package +{ + import flash.geom.Point; + import net.flashpunk.utils.Input; + /** + * ... + * @author jjp + */ + public class Drag + { + private var mouseXLast: int; + private var mouseYLast: int; + private static var drag: Drag = null; + + public function Drag() + { + Update(); + } + + public static function Claim() : Drag + { + if (drag === null) + { + drag = new Drag(); + return drag; + } + return null; + } + public function Delta(zoom:Number = 1): Point + { + return new Point((Input.mouseX - mouseXLast) / zoom, (Input.mouseY - mouseYLast) / zoom); + } + public function Update(): void + { + mouseXLast = Input.mouseX; + mouseYLast = Input.mouseY; + } + public function Done(): void + { + drag = null; + } + } + +} \ No newline at end of file diff --git a/src/EntitySidebar.as b/src/EntitySidebar.as new file mode 100644 index 0000000..6b5dfc6 --- /dev/null +++ b/src/EntitySidebar.as @@ -0,0 +1,32 @@ +package +{ + import net.flashpunk.Entity; + import net.flashpunk.Graphic; + import net.flashpunk.utils.Input; + + /** + * ... + * @author jjp + */ + public class EntitySidebar extends Entity + { + protected var sidebar: Sidebar; + public function EntitySidebar(sidebar: Sidebar, h:int, graphic:Graphic) + { + super(sidebar.x, 0 /*set by sidebar.add*/, graphic); + setHitbox(sidebar.width, h, 0, 0); + this.sidebar = sidebar; + sidebar.Add(this); + } + + override public function update():void + { + super.update(); + if (Input.mousePressed && collidePoint(x, y, Input.mouseX, Input.mouseY)) + OnClick(); + } + public function OnClick(): void { } + public function Fade(pct:Number):void { } + } + +} \ No newline at end of file diff --git a/src/EntitySidebarImg.as b/src/EntitySidebarImg.as new file mode 100644 index 0000000..9e747e3 --- /dev/null +++ b/src/EntitySidebarImg.as @@ -0,0 +1,29 @@ +package +{ + import net.flashpunk.Entity; + import net.flashpunk.Graphic; + import net.flashpunk.graphics.Image; + import net.flashpunk.utils.Input; + + /** + * ... + * @author jjp + */ + public class EntitySidebarImg extends EntitySidebar + { + protected var img: Image; + public function EntitySidebarImg(sidebar: Sidebar, bmp:*) + { + img = new Image(bmp); + img.scale = sidebar.width / img.width; + + super(sidebar, img.scaledHeight, img); + } + override public function Fade(pct:Number):void + { + super.Fade(pct); + img.alpha = pct; + } + } + +} \ No newline at end of file diff --git a/src/EvNewImg.as b/src/EvNewImg.as new file mode 100644 index 0000000..3e6be1f --- /dev/null +++ b/src/EvNewImg.as @@ -0,0 +1,22 @@ +package +{ + import flash.display.BitmapData; + import flash.events.Event; + + /** + * ... + * @author jjp + */ + public class EvNewImg extends Event + { + public var rel: String; + public var bmp: BitmapData; + public function EvNewImg(type: String, rel: String, bmp: BitmapData) + { + this.rel = rel; + this.bmp = bmp; + super(type); + } + } + +} \ No newline at end of file diff --git a/src/Factory.as b/src/Factory.as new file mode 100644 index 0000000..18ca938 --- /dev/null +++ b/src/Factory.as @@ -0,0 +1,35 @@ +package +{ + import flash.display.BitmapData; + import flash.geom.Point; + import net.flashpunk.Entity; + import net.flashpunk.Graphic; + import net.flashpunk.graphics.Image; + import net.flashpunk.Mask; + import net.flashpunk.utils.Input; + + /** + * ... + * @author jjp + */ + public class Factory extends EntitySidebarImg + { + private var bmp: BitmapData; + private var relf: String; + public function Factory(sidebar: Sidebar, relf: String, bmp:BitmapData) + { + super(sidebar, bmp); + this.bmp = bmp; + this.relf = relf; + } + override public function OnClick():void + { + var worldStage: WorldStage = WorldStage(world); + var posMouseReal: Point = worldStage.PointRealFromScreen(new Point(Input.mouseX, Input.mouseY)); + var relfBmp: String = worldStage.RelFull(relf); + var tok: Token = new Token(bmp, relfBmp, posMouseReal.x - this.bmp.width / 2, posMouseReal.y - this.bmp.height / 2); + world.add(tok); + worldStage.tokSelected = tok; + } + } +} \ No newline at end of file diff --git a/src/Folder.as b/src/Folder.as new file mode 100644 index 0000000..34401eb --- /dev/null +++ b/src/Folder.as @@ -0,0 +1,43 @@ +package +{ + import net.flashpunk.Entity; + import net.flashpunk.Graphic; + import net.flashpunk.graphics.Graphiclist; + import net.flashpunk.graphics.Image; + import net.flashpunk.graphics.Text; + import net.flashpunk.Mask; + import net.flashpunk.utils.Input; + + /** + * ... + * @author jjp + */ + public class Folder extends EntitySidebarImg + { + [Embed(source = '../assets/folder.png')] + private const bmpFolder:Class; + + private var reld: String; + private var text: Text; + + public function Folder(sidebar: Sidebar, reld: String) + { + super(sidebar, bmpFolder); + text = new Text(reld, 2, (img.scaledHeight / 2) - 6); + text.color = 0x4444FF; + addGraphic(text); + + this.reld = reld; + } + override public function OnClick():void + { + WorldStage(world).Chdir(this.reld); + } + override public function Fade(pct:Number):void + { + super.Fade(pct); + text.alpha = pct; + } + } + +} \ No newline at end of file diff --git a/src/Imgdir.as b/src/Imgdir.as new file mode 100644 index 0000000..17610ca --- /dev/null +++ b/src/Imgdir.as @@ -0,0 +1,75 @@ +package +{ + import flash.display.BitmapData; + import flash.display.Bitmap; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.FileListEvent; + import flash.events.IOErrorEvent; + import flash.filesystem.File; + import flash.net.URLRequest; + /** + * ... + * @author jjp + */ + public class Imgdir extends EventDispatcher + { + public static const LOADED: String = "ImgLoaded"; + private var dir: File; + private var mprelf_bmp: Object; + private var rgreld: Object; + public function Imgdir(url: String) + { + trace("imgdir: " + url); + this.dir = new File(url); + this.dir.addEventListener(FileListEvent.DIRECTORY_LISTING, OnDirUpdate); + this.mprelf_bmp = { }; + this.rgreld = { }; + } + + public function Update() : void + { + this.dir.getDirectoryListingAsync(); + } + + private function OnDirUpdate(ev:FileListEvent) : void + { + for each (var file: File in ev.files) + { + var rel: String = file.name; + if (file.isDirectory && !rgreld[rel]) + { + rgreld[rel] = true; + dispatchEvent(new EvNewImg(LOADED, rel, null)); + } + else if (!file.isDirectory && /\.(png|gif|jpg|jpeg)$/i.test(rel) && !mprelf_bmp[rel]) + LoadBmp(file, OnBmpLoaded); + } + } + public static function LoadBmp(file: File, dgOnLoad: Function, dgOnFail: Function = null):void + { + var loader : Loader = new Loader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, + function (evBmp: Event) : void { + try + { + var bmp : BitmapData = Bitmap(LoaderInfo(evBmp.target).content).bitmapData; + dgOnLoad(bmp, file); + } + catch(e:*) {} + } ); + if (dgOnFail === null) + dgOnFail = function(): void { }; // ignore errors + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, dgOnFail); + loader.load(new URLRequest(file.url)); + } + private function OnBmpLoaded(bmp: BitmapData, file: File) : void + { + mprelf_bmp[file.name] = bmp; + this.dispatchEvent(new EvNewImg(LOADED, file.name, bmp)); + } + } + +} \ No newline at end of file diff --git a/src/Main.as b/src/Main.as new file mode 100644 index 0000000..e0b24da --- /dev/null +++ b/src/Main.as @@ -0,0 +1,27 @@ +package +{ + import net.flashpunk.Engine; + import net.flashpunk.FP; + import splash.Splash; + import flash.events.FileListEvent + /** + * ... + * @author jjp + */ + public class Main extends Engine + { + public function Main():void + { + super(800, 600, 60, false); + } + + override public function init():void + { + FP.world = new WorldMenu(); + //var worldSplash: Splash = new Splash; + //var worldStage: WorldStage = new WorldStage; + //FP.world.add(worldSplash); + //worldSplash.start(worldStage); + } + } +} \ No newline at end of file diff --git a/src/Sidebar.as b/src/Sidebar.as new file mode 100644 index 0000000..3052b64 --- /dev/null +++ b/src/Sidebar.as @@ -0,0 +1,115 @@ +package +{ + import net.flashpunk.Entity; + import net.flashpunk.Graphic; + import net.flashpunk.Mask; + import net.flashpunk.Tween; + import net.flashpunk.tweens.misc.Alarm; + import net.flashpunk.tweens.misc.NumTween; + import net.flashpunk.tweens.misc.VarTween; + import net.flashpunk.utils.Draw; + import net.flashpunk.utils.Ease; + + /** + * ... + * @author jjp + */ + public class Sidebar extends Entity + { + private var yNew:int; + private var tweenX:VarTween; + private var tweenY:VarTween; + private var tweenFade:NumTween; + private var yLast:Number; + private var alarm:Alarm; + private var xShow:int; + private var xHide:int; + private var yShow:int; + private var yHide:int; + public var fScrollable:Boolean; + public function Sidebar(xShow:int, xHide: int, yShow:int, yHide:int, w:int, h:int, layer:int, fStartShown: Boolean, fScrollable: Boolean) + { + super(fStartShown ? xShow : xHide, fStartShown ? yShow : yHide); + this.xShow = xShow; + this.xHide = xHide; + this.yShow = yShow; + this.yHide = yHide; + this.yLast = y; + this.fScrollable = fScrollable; + setHitbox(w, h, 0, 0); + this.layer = layer + 1; + this.yNew = y; + tweenX = VarTween(addTween(new VarTween(MoveSidebar))); + tweenY = VarTween(addTween(new VarTween(MoveSidebar))); + tweenFade = NumTween(addTween(new NumTween(MoveSidebar))); + alarm = null; + if (fStartShown) + tweenFade.value = 1.0; + else + tweenFade.value = 0.0; + } + public function LayerEntities():int { return this.layer - 1; } + public function Add(entity: EntitySidebar):void + { + entity.y = yNew; + yNew = yNew + entity.height; + entity.layer = LayerEntities(); + world.add(entity); // eehhhhhh + } + public function Die(): void + { + var rgentity: Vector. = new Vector.(); + world.getLayer(LayerEntities(), rgentity); + world.removeList(rgentity); + world.remove(this); + } + override public function render():void + { + Draw.rect(x, y, width, height, 0x7777FF, 0.1); + } + override public function update():void + { + super.update(); + if (tweenX.active) + MoveSidebar(); + } + public function Toggle(dgOnComplete: Function = null): void + { + if (alarm !== null && alarm.active) + removeTween(alarm); + if (x !== xShow || y !== yShow) + { + tweenX.tween(this, "x", xShow, 0.3, Ease.cubeOut); + tweenY.tween(this, "y", yShow, 0.3, Ease.cubeOut); + tweenFade.tween(tweenFade.value, 1.0, 0.3, Ease.cubeOut); + } + else + { + tweenX.tween(this, "x", xHide, 0.3, Ease.cubeIn); + tweenY.tween(this, "y", yHide, 0.3, Ease.cubeIn); + tweenFade.tween(tweenFade.value, 0, 0.3, Ease.cubeIn); + } + if (dgOnComplete !== null) + alarm = Alarm(addTween(new Alarm(0.3, dgOnComplete, Tween.ONESHOT), true)); + } + public function MoveSidebar(dy:int = 0): void + { + if (world !== null) + { + dy = dy + (y - yLast); + var rgentity: Array = []; + world.getLayer(LayerEntities(), rgentity); + for each (var entity: EntitySidebar in rgentity) + { + entity.x = x; + entity.y += dy; + entity.Fade(tweenFade.value); + } + yNew += dy; + yLast = y; + } + } + + } + +} \ No newline at end of file diff --git a/src/TextSidebar.as b/src/TextSidebar.as new file mode 100644 index 0000000..73542e6 --- /dev/null +++ b/src/TextSidebar.as @@ -0,0 +1,25 @@ +package +{ + import net.flashpunk.Graphic; + import net.flashpunk.graphics.Text; + /** + * ... + * @author jjp + */ + public class TextSidebar extends EntitySidebar + { + public function TextSidebar(sidebar:Sidebar, st:String, zoom:int = 1) + { + var text:Text = new Text(st); + text.scale = zoom; + text.x = (sidebar.width / 2) - (text.scaledWidth / 2); + super(sidebar, text.scaledHeight, text); + } + override public function Fade(pct:Number):void + { + super.Fade(pct); + Text(graphic).alpha = pct; + } + } + +} \ No newline at end of file diff --git a/src/Token.as b/src/Token.as new file mode 100644 index 0000000..e011d6b --- /dev/null +++ b/src/Token.as @@ -0,0 +1,137 @@ +package +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.Entity; + import net.flashpunk.graphics.Image; + import net.flashpunk.utils.Draw; + import net.flashpunk.utils.Input; + import net.flashpunk.utils.Key; + + /** + * ... + * @author jjp + */ + public class Token extends Entity + { + private var drag: Drag; + public var posReal: Point; + private var relf: String; + private var xml: XML; + public function Token(source:BitmapData, relf: String, x: int, y: int, xml:XML = null) + { + this.posReal = new Point(x, y); + super(x, y, new Image(source)); + this.type = "Token"; + this.layer = WorldStage.LAYER_TOKENS; + this.drag = null; + this.relf = relf; + if (xml === null) + this.xml = XML(""); + else + this.xml = xml; + } + public function FSelected(): Boolean + { + return WorldStage(this.world).tokSelected === this; + } + override public function removed():void + { + super.removed(); + if (drag !== null) + { + drag.Done(); + drag = null; + } + } + override public function added():void + { + super.added(); + FixupZoom(); + } + private function FixupZoom(): Number + { + var worldStage: WorldStage = WorldStage(world); + var zoom: Number = worldStage.zoom; + var img: Image = Image(this.graphic); + Image(this.graphic).scale = zoom; + var posScreen : Point = worldStage.PointScreenFromReal(posReal); + x = int(posScreen.x); + y = int(posScreen.y); + this.setHitbox(img.scaledWidth, img.scaledHeight, -img.x, -img.y); + + return zoom; + } + override public function update():void + { + super.update(); + + var zoom: Number = FixupZoom(); + + if (Input.mouseUp && drag !== null) + { + trace("drag done"); + drag.Done(); + drag = null; + } + + if (FSelected()) + { + var deltaMove: Point = null; + if (Input.pressed(Key.UP)) + deltaMove = new Point(0, -1); + else if (Input.pressed(Key.DOWN)) + deltaMove = new Point(0, 1); + else if (Input.pressed(Key.LEFT)) + deltaMove = new Point(-1, 0); + else if (Input.pressed(Key.RIGHT)) + deltaMove = new Point(1, 0); + + if (Input.pressed(Key.PAGE_UP)) + world.bringForward(this); + else if (Input.pressed(Key.PAGE_DOWN)) + world.sendBackward(this); + + if (Input.mouseDown) + { + if (drag === null && collidePoint(x, y, Input.mouseX, Input.mouseY)) + { + drag = Drag.Claim(); + if (drag !== null) + trace("drag claimed for " + relf); + else + trace("drag failed for " + relf); + } + + if (drag !== null) + { + deltaMove = drag.Delta(zoom); + drag.Update(); + } + } + if (deltaMove !== null) + posReal = posReal.add(deltaMove); + + if (Input.pressed(Key.DELETE)) + WorldStage(world).KillToken(this); + } + } + override public function render():void + { + super.render(); + if (FSelected()) + Draw.hitbox(this); + } + + public function GenXML(): XML + { + var xml: XML = this.xml.copy(); + xml.@x = int(posReal.x); + xml.@y = int(posReal.y); + xml.@path = relf; + return xml; + } + } + +} \ No newline at end of file diff --git a/src/WorldMenu.as b/src/WorldMenu.as new file mode 100644 index 0000000..38c36ca --- /dev/null +++ b/src/WorldMenu.as @@ -0,0 +1,98 @@ +package +{ + import flash.events.Event; + import flash.filesystem.File; + import flash.net.FileFilter; + import flash.net.FileReference; + import flash.net.FileReferenceList; + import flash.net.URLRequest; + import flash.utils.Dictionary; + import net.flashpunk.Entity; + import net.flashpunk.FP; + import net.flashpunk.graphics.Image; + import net.flashpunk.graphics.Text; + import net.flashpunk.utils.Input; + import net.flashpunk.World; + + /** + * ... + * @author jjp + */ + public class WorldMenu extends World + { + [Embed(source = '../assets/punk.png')] + private const bmpPunk:Class; + + private var mpentity_dgclick: Dictionary; + + public function WorldMenu() + { + super(); + AddText("KlikPunk", FP.height / 8, 5); + AddText("icons by Mark James http://www.famfamfam.com/lab/icons/silk/", FP.height - 32); + var imgPunk: Image = new Image(bmpPunk); + imgPunk.scale = 8; + var entityPunk: Entity = new Entity((FP.width / 2) - (imgPunk.scaledWidth / 2), 10, imgPunk); + entityPunk.layer = 10; + add(entityPunk); + SetMenu(["New Stage", NewStage], ["Open Stage", OpenStage]); + } + private function SetMenu(...rgmenu):void + { + var yMenu:int = (FP.height / 2); + var hMenus:int = FP.height - yMenu - (FP.height / 8); + var dyMenu:int = hMenus / rgmenu.length; + mpentity_dgclick = new Dictionary(); + for each(var menu:Array in rgmenu) + { + var entityMenu: Entity = AddText(menu[0], yMenu, 2); + entityMenu.type = "menuitem"; + mpentity_dgclick[entityMenu] = menu[1]; + yMenu = yMenu + dyMenu; + } + } + private function AddText(stText:String, y:int, scale:Number = 1): Entity + { + var text: Text = new Text(stText); + text.scale = scale; + var entity: Entity = addGraphic(text, 0, (FP.width / 2) - (text.scaledWidth / 2), y - (text.scaledHeight / 2)); + entity.setHitbox(text.scaledWidth, text.scaledHeight, 0, 0); + return entity; + } + public function NewStage(): void + { + FileForBrowse().browseForSave("Choose your destiny"); + } + public function OpenStage(): void + { + FileForBrowse().browseForOpen("Find your thing", [new FileFilter("Stages", "*.xml")]); + } + private function FileForBrowse(): File + { + var file: File = new File(File.userDirectory.nativePath + File.separator + "NewStage.xml"); + file.addEventListener(Event.SELECT, function():void { + FP.world = new WorldStage(file.url); + }); + + return file; + } + override public function update():void + { + var rgentityMenu: Array = []; + getType("menuitem", rgentityMenu); + for each(var entityMenu: Entity in rgentityMenu) + { + if (entityMenu.collidePoint(entityMenu.x, entityMenu.y, Input.mouseX, Input.mouseY)) + { + if (Input.mousePressed) + mpentity_dgclick[entityMenu](); + Text(entityMenu.graphic).color = 0xFFFFFF; + } + else + Text(entityMenu.graphic).color = 0x888888; + } + super.update(); + } + } + +} \ No newline at end of file diff --git a/src/WorldStage.as b/src/WorldStage.as new file mode 100644 index 0000000..7ce7019 --- /dev/null +++ b/src/WorldStage.as @@ -0,0 +1,308 @@ +package +{ + import flash.display.BitmapData; + import flash.events.Event; + import flash.filesystem.File; + import flash.filesystem.FileMode; + import flash.filesystem.FileStream; + import flash.geom.Point; + import net.flashpunk.debug.Console; + import net.flashpunk.Entity; + import net.flashpunk.FP; + import net.flashpunk.graphics.Image; + import net.flashpunk.Tween; + import net.flashpunk.tweens.misc.Alarm; + import net.flashpunk.tweens.misc.VarTween; + import net.flashpunk.utils.Ease; + import net.flashpunk.utils.Key; + import net.flashpunk.World; + import net.flashpunk.utils.Input; + /** + * ... + * @author jjp + */ + public class WorldStage extends World + { + [Embed(source = '../assets/save.png')] + private const bmpSave:Class; + + public static const LAYER_TOKENS: int = 100; + public static const LAYER_SIDEBAR: int = 50; + public static const LAYER_SAVE: int = 60; + public static const LAYER_MSG: int = 70; + + private var imgdir: Imgdir; + private var drag: Drag; + private var absd: String; + private var relf: String; + private var rgreld: Array; + private var alarmImgdir: Tween; + private var rgsidebar: Vector.; + + private var sidebarMsg: Sidebar; + private var rgmsg: Vector.; + private var alarmMsg: Alarm; + + public var tokSelected: Token; + public var zoom: Number; + public var pointView: Point; + + public function WorldStage(absf: String) + { + super(); + var match:* = /(.*\/)([^\/]*)$/.exec(absf); + this.absd = match[1]; + this.relf = match[2]; + this.rgreld = []; + this.rgsidebar = new Vector.(); + + var sidebarSave: Sidebar = AddSidebar(new Sidebar(FP.width - 32, FP.width, 0, 0, 32, 32, LAYER_SAVE, false, false)); + new Button(sidebarSave, bmpSave, Save); + + sidebarMsg = null; + alarmMsg = null; + rgmsg = new Vector.() + + zoom = 1; + pointView = new Point(0, 0); + drag = null; + Chdir(null); + ToggleUI(); + Load(); + } + + public function KillToken(tok: Token): void + { + remove(tok); + tok.active = false; + if (tokSelected === tok) + tokSelected = null; + } + public function PointRealFromScreen(pointScreen: Point): Point + { + return new Point((pointScreen.x / zoom) + pointView.x, (pointScreen.y / zoom) + pointView.y); + } + public function PointScreenFromReal(pointReal: Point) : Point + { + return new Point((pointReal.x - pointView.x) * zoom, (pointReal.y - pointView.y) * zoom); + } + private function OnNewImg(ev: EvNewImg) : void + { + var entity: Entity; + if (ev.bmp === null) + entity = new Folder(SidebarFind(LAYER_SIDEBAR), ev.rel); + else + entity = new Factory(SidebarFind(LAYER_SIDEBAR), ev.rel, ev.bmp); + } + public function RelFull(relf:String = null): String + { + var rel:String = rgreld.join(File.separator); + if (relf !== null) + rel = rel + File.separator + relf; + return rel; + } + public function Chdir(reld: String): void + { + if (alarmImgdir !== null) + { + removeTween(alarmImgdir); + + var rgui: Array = []; + getLayer(LAYER_SIDEBAR, rgui); + for each (var entity: Entity in rgui) + { + remove(entity); + entity.active = false; + } + } + if (reld === "..") + rgreld = rgreld.slice(0, rgreld.length - 1); + else if (reld !== null) + rgreld.push(reld); + + imgdir = new Imgdir(absd + RelFull()); + imgdir.addEventListener(Imgdir.LOADED, OnNewImg); + imgdir.Update(); + alarmImgdir = this.addTween(new Alarm(3, imgdir.Update, Tween.LOOPING), true); + + AddSidebar(new Sidebar(0, -32, 0, 0, 32, FP.height, LAYER_SIDEBAR, reld !== null /*fStartShown*/, true)); + + if (rgreld.length > 0) + OnNewImg(new EvNewImg(Imgdir.LOADED, "..", null)); + } + private function SidebarFind(layer: int):Sidebar + { + for each (var sidebar:Sidebar in rgsidebar) + if (sidebar.LayerEntities() === layer) + return sidebar; + return null; + } + private function RemoveSidebar(layer:int):void + { + rgsidebar = rgsidebar.filter( + function(sidebarOld:Sidebar, isidebarOld:int, rg:Vector.):Boolean { + if (sidebarOld.LayerEntities() === layer) + { + sidebarOld.Die(); + return false; + } + return true; + }, this); + } + private function AddSidebar(sidebar: Sidebar):Sidebar + { + RemoveSidebar(sidebar.LayerEntities()); + add(sidebar); + rgsidebar.push(sidebar); + return sidebar; + } + private function ToggleUI(): void + { + for each (var sidebar: Sidebar in rgsidebar) + sidebar.Toggle(); + } + override public function update():void + { + var dyFactory:int = 0; + if (Input.mousePressed) + { + var rgtok: Array = []; + getLayer(LAYER_TOKENS, rgtok); + tokSelected = null; + // getLayer returns tokens in draw order, which means furthest back first + for (var itok:int = rgtok.length - 1; itok >= 0; itok --) + { + var tok: Token = rgtok[itok]; + if (tok.collidePoint(tok.x, tok.y, Input.mouseX, Input.mouseY)) + { + tokSelected = tok; + break; + } + } + trace("clicked on " + tokSelected); + } + if (Input.pressed(Key.TAB)) + ToggleUI(); + if (Input.mouseWheel) + { + var fSidebarScrolled:Boolean = false; + for each (var sidebar: Sidebar in rgsidebar) + { + if (sidebar.fScrollable && sidebar.collidePoint(sidebar.x, sidebar.y, Input.mouseX, Input.mouseY)) + { + sidebar.MoveSidebar(Input.mouseWheelDelta * 5); + fSidebarScrolled = true; + break; + } + } + if (!fSidebarScrolled) + { + var zoomNew: Number = zoom + (Input.mouseWheelDelta / 100); + if (zoomNew <= 0) + zoomNew = 0.01; + // keep the point under the mouse cursor in the same place + var dx: Number = (Input.mouseX / zoom) - (Input.mouseX / zoomNew); + var dy: Number = (Input.mouseY / zoom) - (Input.mouseY / zoomNew); + pointView.x = pointView.x + dx; + pointView.y = pointView.y + dy; + zoom = zoomNew; + } + } + super.update(); + } + + public function ShowMsg(stMsg: String):void + { + rgmsg.push(stMsg); + if (alarmMsg === null) + ShowNextMsg(); + } + private function ShowNextMsg(): void + { + if (sidebarMsg !== null) + { + sidebarMsg.Die(); + sidebarMsg = null; + } + if (alarmMsg !== null && alarmMsg.active) + removeTween(alarmMsg); + alarmMsg = null; + + if (rgmsg.length > 0) + { + var stMsg:String = rgmsg.shift(); + var x:int = FP.width / 8; + sidebarMsg = Sidebar(add(new Sidebar(x, x, FP.height - 45, FP.height, x * 6, 45, LAYER_MSG, false, false))); + new TextSidebar(sidebarMsg, stMsg, 2); + sidebarMsg.Toggle(); + alarmMsg = Alarm(addTween(new Alarm(2, function():void { sidebarMsg.Toggle(ShowNextMsg); }, Tween.ONESHOT), true)); + } + } + + public function Save(): void + { + var stream: FileStream = new FileStream(); + stream.open(new File(absd + File.separator + relf), FileMode.WRITE); + stream.writeUTFBytes(GenXML().toXMLString()); + stream.close(); + ShowMsg("Saved."); + } + public function Load(): void + { + var file: File = new File(absd + File.separator + relf); + if (file.exists) + { + var stream: FileStream = new FileStream(); + stream.open(file, FileMode.READ); + var xml: XML = XML(stream.readUTFBytes(file.size)); + var itoken:int = 0; + var rgtoken:Object = { ctokenLoaded: 0, ctoken: xml.children().length(), rgtoken: [] }; + for each (var xmlToken: XML in xml.children()) + { + LoadToken(xmlToken, itoken, rgtoken); + itoken = itoken + 1; + } + ShowMsg("Loaded."); + } + } + private function LoadToken(xml: XML, itoken: int, rgtoken:Object): void + { + trace("loading " + xml.@path); + Imgdir.LoadBmp(new File(absd + File.separator + xml.@path), + function(bmp: BitmapData, file: File):void { + trace("loadtoken: " + xml.@path); + var token: Token = new Token(bmp, xml.@path.toString(), int(xml.@x), int(xml.@y), xml); + FixupTokens(rgtoken, token, itoken); + }, + function():void + { + FixupTokens(rgtoken, null, itoken); + }); + } + private function FixupTokens(rgtoken:Object, tokenLoaded: Token, itokenLoaded:int):void + { + rgtoken.ctokenLoaded ++; + rgtoken.rgtoken[itokenLoaded] = tokenLoaded; + if (rgtoken.ctokenLoaded === rgtoken.ctoken) + { + for (var itoken:int = 0; itoken < rgtoken.ctoken; itoken ++) + { + var token:Token = rgtoken.rgtoken[itoken]; + if (token !== null) + add(token); + } + } + } + public function GenXML():XML + { + var xml: XML = XML(""); + var rgtoken:Array = []; + this.getLayer(LAYER_TOKENS, rgtoken); + for each(var token: Token in rgtoken) + xml.appendChild(token.GenXML()); + + return xml; + } + } + +} \ No newline at end of file diff --git a/src/net/flashpunk/Engine.as b/src/net/flashpunk/Engine.as new file mode 100644 index 0000000..f243ec3 --- /dev/null +++ b/src/net/flashpunk/Engine.as @@ -0,0 +1,291 @@ +package net.flashpunk +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.MovieClip; + import flash.display.StageAlign; + import flash.display.StageDisplayState; + import flash.display.StageQuality; + import flash.display.StageScaleMode; + import flash.events.Event; + import flash.events.TimerEvent; + import flash.geom.Rectangle; + import flash.utils.getTimer; + import flash.utils.Timer; + import net.flashpunk.utils.Draw; + import net.flashpunk.utils.Input; + + /** + * Main game Sprite class, added to the Flash Stage. Manages the game loop. + */ + public class Engine extends MovieClip + { + /** + * If the game should stop updating/rendering. + */ + public var paused:Boolean = false; + + /** + * Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10). + */ + public var maxElapsed:Number = 0.0333; + + /** + * The max amount of frames that can be skipped in fixed framerate mode. + */ + public var maxFrameSkip:uint = 5; + + /** + * The amount of milliseconds between ticks in fixed framerate mode. + */ + public var tickRate:uint = 4; + + /** + * Constructor. Defines startup information about your game. + * @param width The width of your game. + * @param height The height of your game. + * @param frameRate The game framerate, in frames per second. + * @param fixed If a fixed-framerate should be used. + */ + public function Engine(width:uint, height:uint, frameRate:Number = 60, fixed:Boolean = false) + { + // global game properties + FP.width = width; + FP.height = height; + FP.assignedFrameRate = frameRate; + FP.fixed = fixed; + + // global game objects + FP.engine = this; + FP.screen = new Screen; + FP.bounds = new Rectangle(0, 0, width, height); + FP._world = new World; + + // miscellanious startup stuff + if (FP.randomSeed == 0) FP.randomizeSeed(); + FP.entity = new Entity; + FP._time = getTimer(); + + // on-stage event listener + addEventListener(Event.ADDED_TO_STAGE, onStage); + } + + /** + * Override this, called after Engine has been added to the stage. + */ + public function init():void + { + + } + + /** + * Updates the game, updating the World and Entities. + */ + public function update():void + { + if (FP._world.active) + { + if (FP._world._tween) FP._world.updateTweens(); + FP._world.update(); + } + FP._world.updateLists(); + if (FP._goto) checkWorld(); + } + + /** + * Renders the game, rendering the World and Entities. + */ + public function render():void + { + // timing stuff + var t:Number = getTimer(); + if (!_frameLast) _frameLast = t; + + // render loop + FP.screen.swap(); + Draw.resetTarget(); + FP.screen.refresh(); + if (FP._world.visible) FP._world.render(); + FP.screen.redraw(); + + // more timing stuff + t = getTimer(); + _frameListSum += (_frameList[_frameList.length] = t - _frameLast); + if (_frameList.length > 10) _frameListSum -= _frameList.shift(); + FP.frameRate = 1000 / (_frameListSum / _frameList.length); + _frameLast = t; + } + + /** + * Sets the game's stage properties. Override this to set them differently. + */ + public function setStageProperties():void + { + stage.frameRate = FP.assignedFrameRate; + stage.align = StageAlign.TOP_LEFT; + stage.quality = StageQuality.HIGH; + stage.scaleMode = StageScaleMode.NO_SCALE; + stage.displayState = StageDisplayState.NORMAL; + } + + /** @private Event handler for stage entry. */ + private function onStage(e:Event = null):void + { + // remove event listener + removeEventListener(Event.ADDED_TO_STAGE, onStage); + + // set stage properties + FP.stage = stage; + setStageProperties(); + + // enable input + Input.enable(); + + // switch worlds + if (FP._goto) checkWorld(); + + // game start + init(); + + // start game loop + _rate = 1000 / FP.assignedFrameRate; + if (FP.fixed) + { + // fixed framerate + _skip = _rate * maxFrameSkip; + _last = _prev = getTimer(); + _timer = new Timer(tickRate); + _timer.addEventListener(TimerEvent.TIMER, onTimer); + _timer.start(); + } + else + { + // nonfixed framerate + _last = getTimer(); + addEventListener(Event.ENTER_FRAME, onEnterFrame); + } + } + + /** @private Framerate independent game loop. */ + private function onEnterFrame(e:Event):void + { + // update timer + _time = _gameTime = getTimer(); + FP._flashTime = _time - _flashTime; + _updateTime = _time; + FP.elapsed = (_time - _last) / 1000; + if (FP.elapsed > maxElapsed) FP.elapsed = maxElapsed; + FP.elapsed *= FP.rate; + _last = _time; + + // update console + if (FP._console) FP._console.update(); + + // update loop + if (!paused) update(); + + // update input + Input.update(); + + // update timer + _time = _renderTime = getTimer(); + FP._updateTime = _time - _updateTime; + + // render loop + if (!paused) render(); + + // update timer + _time = _flashTime = getTimer(); + FP._renderTime = _time - _renderTime; + FP._gameTime = _time - _gameTime; + } + + /** @private Fixed framerate game loop. */ + private function onTimer(e:TimerEvent):void + { + // update timer + _time = getTimer(); + _delta += (_time - _last); + _last = _time; + + // quit if a frame hasn't passed + if (_delta < _rate) return; + + // update timer + _gameTime = _time; + FP._flashTime = _time - _flashTime; + + // update console + if (FP._console) FP._console.update(); + + // update loop + if (_delta > _skip) _delta = _skip; + while (_delta > _rate) + { + // update timer + _updateTime = _time; + _delta -= _rate; + FP.elapsed = (_time - _prev) / 1000; + if (FP.elapsed > maxElapsed) FP.elapsed = maxElapsed; + FP.elapsed *= FP.rate; + _prev = _time; + + // update loop + if (!paused) update(); + + // update input + Input.update(); + + // update timer + _time = getTimer(); + FP._updateTime = _time - _updateTime; + } + + // update timer + _renderTime = _time; + + // render loop + if (!paused) render(); + + // update timer + _time = _flashTime = getTimer(); + FP._renderTime = _time - _renderTime; + FP._gameTime = _time - _gameTime; + } + + /** @private Switch Worlds if they've changed. */ + private function checkWorld():void + { + if (!FP._goto) return; + FP._world.end(); + FP._world.updateLists(); + if (FP._world && FP._world.autoClear && FP._world._tween) FP._world.clearTweens(); + FP._world = FP._goto; + FP._goto = null; + FP.camera = FP._world.camera; + FP._world.updateLists(); + FP._world.begin(); + FP._world.updateLists(); + } + + // Timing information. + /** @private */ private var _delta:Number = 0; + /** @private */ private var _time:Number; + /** @private */ private var _last:Number; + /** @private */ private var _timer:Timer; + /** @private */ private var _rate:Number; + /** @private */ private var _skip:Number; + /** @private */ private var _prev:Number; + + // Debug timing information. + /** @private */ private var _updateTime:uint; + /** @private */ private var _renderTime:uint; + /** @private */ private var _gameTime:uint; + /** @private */ private var _flashTime:uint; + + // FrameRate tracking. + /** @private */ private var _frameLast:uint = 0; + /** @private */ private var _frameListSum:uint = 0; + /** @private */ private var _frameList:Vector. = new Vector.; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Entity.as b/src/net/flashpunk/Entity.as new file mode 100644 index 0000000..cf80563 --- /dev/null +++ b/src/net/flashpunk/Entity.as @@ -0,0 +1,764 @@ +package net.flashpunk +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.getQualifiedClassName; + import flash.utils.getDefinitionByName; + import net.flashpunk.masks.*; + import net.flashpunk.graphics.*; + + /** + * Main game Entity class updated by World. + */ + public class Entity extends Tweener + { + /** + * If the Entity should render. + */ + public var visible:Boolean = true; + + /** + * If the Entity should respond to collision checks. + */ + public var collidable:Boolean = true; + + /** + * X position of the Entity in the World. + */ + public var x:Number = 0; + + /** + * Y position of the Entity in the World. + */ + public var y:Number = 0; + + /** + * Width of the Entity's hitbox. + */ + public var width:int; + + /** + * Height of the Entity's hitbox. + */ + public var height:int; + + /** + * X origin of the Entity's hitbox. + */ + public var originX:int; + + /** + * Y origin of the Entity's hitbox. + */ + public var originY:int; + + /** + * The BitmapData target to draw the Entity to. Leave as null to render to the current screen buffer (default). + */ + public var renderTarget:BitmapData; + + /** + * Constructor. Can be usd to place the Entity and assign a graphic and mask. + * @param x X position to place the Entity. + * @param y Y position to place the Entity. + * @param graphic Graphic to assign to the Entity. + * @param mask Mask to assign to the Entity. + */ + public function Entity(x:Number = 0, y:Number = 0, graphic:Graphic = null, mask:Mask = null) + { + this.x = x; + this.y = y; + if (graphic) this.graphic = graphic; + if (mask) this.mask = mask; + HITBOX.assignTo(this); + _class = Class(getDefinitionByName(getQualifiedClassName(this))); + } + + /** + * Override this, called when the Entity is added to a World. + */ + public function added():void + { + + } + + /** + * Override this, called when the Entity is removed from a World. + */ + public function removed():void + { + + } + + /** + * Updates the Entity. + */ + override public function update():void + { + + } + + /** + * Renders the Entity. If you override this for special behaviour, + * remember to call super.render() to render the Entity's graphic. + */ + public function render():void + { + if (_graphic && _graphic.visible) + { + if (_graphic.relative) + { + _point.x = x; + _point.y = y; + } + else _point.x = _point.y = 0; + _camera.x = FP.camera.x; + _camera.y = FP.camera.y; + _graphic.render(renderTarget ? renderTarget : FP.buffer, _point, _camera); + } + } + + /** + * Checks for a collision against an Entity type. + * @param type The Entity type to check for. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @return The first Entity collided with, or null if none were collided. + */ + public function collide(type:String, x:Number, y:Number):Entity + { + if (!_world) return null; + + var e:Entity = _world._typeFirst[type]; + if (!collidable || !e) return null; + + _x = this.x; _y = this.y; + this.x = x; this.y = y; + + if (!_mask) + { + while (e) + { + if (x - originX + width > e.x - e.originX + && y - originY + height > e.y - e.originY + && x - originX < e.x - e.originX + e.width + && y - originY < e.y - e.originY + e.height + && e.collidable && e !== this) + { + if (!e._mask || e._mask.collide(HITBOX)) + { + this.x = _x; this.y = _y; + return e; + } + } + e = e._typeNext; + } + this.x = _x; this.y = _y; + return null; + } + + while (e) + { + if (x - originX + width > e.x - e.originX + && y - originY + height > e.y - e.originY + && x - originX < e.x - e.originX + e.width + && y - originY < e.y - e.originY + e.height + && e.collidable && e !== this) + { + if (_mask.collide(e._mask ? e._mask : e.HITBOX)) + { + this.x = _x; this.y = _y; + return e; + } + } + e = e._typeNext; + } + this.x = _x; this.y = _y; + return null; + } + + /** + * Checks for collision against multiple Entity types. + * @param types An Array or Vector of Entity types to check for. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @return The first Entity collided with, or null if none were collided. + */ + public function collideTypes(types:Object, x:Number, y:Number):Entity + { + if (!_world) return null; + var e:Entity; + for each (var type:String in types) + { + if ((e = collide(type, x, y))) return e; + } + return null; + } + + /** + * Checks if this Entity collides with a specific Entity. + * @param e The Entity to collide against. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @return The Entity if they overlap, or null if they don't. + */ + public function collideWith(e:Entity, x:Number, y:Number):Entity + { + _x = this.x; _y = this.y; + this.x = x; this.y = y; + + if (x - originX + width > e.x - e.originX + && y - originY + height > e.y - e.originY + && x - originX < e.x - e.originX + e.width + && y - originY < e.y - e.originY + e.height + && collidable && e.collidable) + { + if (!_mask) + { + if (!e._mask || e._mask.collide(HITBOX)) + { + this.x = _x; this.y = _y; + return e; + } + this.x = _x; this.y = _y; + return null; + } + if (_mask.collide(e._mask ? e._mask : e.HITBOX)) + { + this.x = _x; this.y = _y; + return e; + } + } + this.x = _x; this.y = _y; + return null; + } + + /** + * Checks if this Entity overlaps the specified rectangle. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @param rX X position of the rectangle. + * @param rY Y position of the rectangle. + * @param rWidth Width of the rectangle. + * @param rHeight Height of the rectangle. + * @return If they overlap. + */ + public function collideRect(x:Number, y:Number, rX:Number, rY:Number, rWidth:Number, rHeight:Number):Boolean + { + if (x - originX + width >= rX && y - originY + height >= rY + && x - originX <= rX + rWidth && y - originY <= rY + rHeight) + { + if (!_mask) return true; + _x = this.x; _y = this.y; + this.x = x; this.y = y; + FP.entity.x = rX; + FP.entity.y = rY; + FP.entity.width = rWidth; + FP.entity.height = rHeight; + if (_mask.collide(FP.entity.HITBOX)) + { + this.x = _x; this.y = _y; + return true; + } + this.x = _x; this.y = _y; + return false; + } + return false; + } + + /** + * Checks if this Entity overlaps the specified position. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @param pX X position. + * @param pY Y position. + * @return If the Entity intersects with the position. + */ + public function collidePoint(x:Number, y:Number, pX:Number, pY:Number):Boolean + { + if (pX >= x - originX && pY >= y - originY + && pX < x - originX + width && pY < y - originY + height) + { + if (!_mask) return true; + _x = this.x; _y = this.y; + this.x = x; this.y = y; + FP.entity.x = pX; + FP.entity.y = pY; + FP.entity.width = 1; + FP.entity.height = 1; + if (_mask.collide(FP.entity.HITBOX)) + { + this.x = _x; this.y = _y; + return true; + } + this.x = _x; this.y = _y; + return false; + } + return false; + } + + /** + * Populates an array with all collided Entities of a type. + * @param type The Entity type to check for. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @param array The Array or Vector object to populate. + * @return The array, populated with all collided Entities. + */ + public function collideInto(type:String, x:Number, y:Number, array:Object):void + { + if (!_world) return; + + var e:Entity = _world._typeFirst[type]; + if (!collidable || !e) return; + + _x = this.x; _y = this.y; + this.x = x; this.y = y; + var n:uint = array.length; + + if (!_mask) + { + while (e) + { + if (x - originX + width > e.x - e.originX + && y - originY + height > e.y - e.originY + && x - originX < e.x - e.originX + e.width + && y - originY < e.y - e.originY + e.height + && e.collidable && e !== this) + { + if (!e._mask || e._mask.collide(HITBOX)) array[n ++] = e; + } + e = e._typeNext; + } + this.x = _x; this.y = _y; + return; + } + + while (e) + { + if (x - originX + width > e.x - e.originX + && y - originY + height > e.y - e.originY + && x - originX < e.x - e.originX + e.width + && y - originY < e.y - e.originY + e.height + && e.collidable && e !== this) + { + if (_mask.collide(e._mask ? e._mask : e.HITBOX)) array[n ++] = e; + } + e = e._typeNext; + } + this.x = _x; this.y = _y; + return; + } + + /** + * Populates an array with all collided Entities of multiple types. + * @param types An array of Entity types to check for. + * @param x Virtual x position to place this Entity. + * @param y Virtual y position to place this Entity. + * @param array The Array or Vector object to populate. + * @return The array, populated with all collided Entities. + */ + public function collideTypesInto(types:Object, x:Number, y:Number, array:Object):void + { + if (!_world) return; + for each (var type:String in types) collideInto(type, x, y, array); + } + + /** + * If the Entity collides with the camera rectangle. + */ + public function get onCamera():Boolean + { + return collideRect(x, y, FP.camera.x, FP.camera.y, FP.width, FP.height); + } + + /** + * The World object this Entity has been added to. + */ + public function get world():World + { + return _world; + } + + /** + * Half the Entity's width. + */ + public function get halfWidth():Number { return width / 2; } + + /** + * Half the Entity's height. + */ + public function get halfHeight():Number { return height / 2; } + + /** + * The center x position of the Entity's hitbox. + */ + public function get centerX():Number { return x - originX + width / 2; } + + /** + * The center y position of the Entity's hitbox. + */ + public function get centerY():Number { return y - originY + height / 2; } + + /** + * The leftmost position of the Entity's hitbox. + */ + public function get left():Number { return x - originX; } + + /** + * The rightmost position of the Entity's hitbox. + */ + public function get right():Number { return x - originX + width; } + + /** + * The topmost position of the Entity's hitbox. + */ + public function get top():Number { return y - originY; } + + /** + * The bottommost position of the Entity's hitbox. + */ + public function get bottom():Number { return y - originY + height; } + + /** + * The rendering layer of this Entity. Higher layers are rendered first. + */ + public function get layer():int { return _layer; } + public function set layer(value:int):void + { + if (_layer == value) return; + if (!_added) + { + _layer = value; + return; + } + _world.removeRender(this); + _layer = value; + _world.addRender(this); + } + + /** + * The collision type, used for collision checking. + */ + public function get type():String { return _type; } + public function set type(value:String):void + { + if (_type == value) return; + if (!_added) + { + _type = value; + return; + } + if (_type) _world.removeType(this); + _type = value; + if (value) _world.addType(this); + } + + /** + * An optional Mask component, used for specialized collision. If this is + * not assigned, collision checks will use the Entity's hitbox by default. + */ + public function get mask():Mask { return _mask; } + public function set mask(value:Mask):void + { + if (_mask == value) return; + if (_mask) _mask.assignTo(null); + _mask = value; + if (value) _mask.assignTo(this); + } + + /** + * Graphical component to render to the screen. + */ + public function get graphic():Graphic { return _graphic; } + public function set graphic(value:Graphic):void + { + if (_graphic == value) return; + _graphic = value; + if (value && value._assign != null) value._assign(); + } + + /** + * Adds the graphic to the Entity via a Graphiclist. + * @param g Graphic to add. + */ + public function addGraphic(g:Graphic):Graphic + { + if (!(graphic is Graphiclist)) + { + var list:Graphiclist = new Graphiclist; + if (graphic) list.add(graphic); + graphic = list; + } + (graphic as Graphiclist).add(g); + return g; + } + + /** + * Sets the Entity's hitbox properties. + * @param width Width of the hitbox. + * @param height Height of the hitbox. + * @param originX X origin of the hitbox. + * @param originY Y origin of the hitbox. + */ + public function setHitbox(width:int = 0, height:int = 0, originX:int = 0, originY:int = 0):void + { + this.width = width; + this.height = height; + this.originX = originX; + this.originY = originY; + } + + /** + * Sets the Entity's hitbox to match that of the provided object. + * @param o The object defining the hitbox (eg. an Image or Rectangle). + */ + public function setHitboxTo(o:Object):void + { + if (o is Image || o is Rectangle) setHitbox(o.width, o.height, -o.x, -o.y); + else + { + if (o.hasOwnProperty("width")) width = o.width; + if (o.hasOwnProperty("height")) height = o.height; + if (o.hasOwnProperty("originX") && !(o is Graphic)) originX = o.originX; + else if (o.hasOwnProperty("x")) originX = -o.x; + if (o.hasOwnProperty("originY") && !(o is Graphic)) originX = o.originY; + else if (o.hasOwnProperty("y")) originX = -o.y; + } + } + + /** + * Sets the origin of the Entity. + * @param x X origin. + * @param y Y origin. + */ + public function setOrigin(x:int = 0, y:int = 0):void + { + originX = x; + originY = y; + } + + /** + * Center's the Entity's origin (half width & height). + */ + public function centerOrigin():void + { + originX = width / 2; + originY = height / 2; + } + + /** + * Calculates the distance from another Entity. + * @param e The other Entity. + * @param useHitboxes If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used. + * @return The distance. + */ + public function distanceFrom(e:Entity, useHitboxes:Boolean = false):Number + { + if (!useHitboxes) return Math.sqrt((x - e.x) * (x - e.x) + (y - e.y) * (y - e.y)); + return FP.distanceRects(x - originX, y - originY, width, height, e.x - e.originX, e.y - e.originY, e.width, e.height); + } + + /** + * Calculates the distance from this Entity to the point. + * @param px X position. + * @param py Y position. + * @param useHitboxes If hitboxes should be used to determine the distance. If not, the Entities' x/y positions are used. + * @return The distance. + */ + public function distanceToPoint(px:Number, py:Number, useHitbox:Boolean = false):Number + { + if (!useHitbox) return Math.sqrt((x - px) * (x - px) + (y - py) * (y - py)); + return FP.distanceRectPoint(px, py, x - originX, y - originY, width, height); + } + + /** + * Calculates the distance from this Entity to the rectangle. + * @param rx X position of the rectangle. + * @param ry Y position of the rectangle. + * @param rwidth Width of the rectangle. + * @param rheight Height of the rectangle. + * @return The distance. + */ + public function distanceToRect(rx:Number, ry:Number, rwidth:Number, rheight:Number):Number + { + return FP.distanceRects(rx, ry, rwidth, rheight, x - originX, y - originY, width, height); + } + + /** + * Gets the class name as a string. + * @return A string representing the class name. + */ + public function toString():String + { + var s:String = String(_class); + return s.substring(7, s.length - 1); + } + + /** + * Moves the Entity by the amount, retaining integer values for its x and y. + * @param x Horizontal offset. + * @param y Vertical offset. + * @param solidType An optional collision type to stop flush against upon collision. + * @param sweep If sweeping should be used (prevents fast-moving objects from going through solidType). + */ + public function moveBy(x:Number, y:Number, solidType:String = null, sweep:Boolean = false):void + { + _moveX += x; + _moveY += y; + x = Math.round(_moveX); + y = Math.round(_moveY); + _moveX -= x; + _moveY -= y; + if (solidType) + { + var sign:int, e:Entity; + if (x != 0) + { + if (collidable && (sweep || collide(solidType, this.x + x, this.y))) + { + sign = x > 0 ? 1 : -1; + while (x != 0) + { + if ((e = collide(solidType, this.x + sign, this.y))) + { + moveCollideX(e); + break; + } + else + { + this.x += sign; + x -= sign; + } + } + } + else this.x += x; + } + if (y != 0) + { + if (collidable && (sweep || collide(solidType, this.x, this.y + y))) + { + sign = y > 0 ? 1 : -1; + while (y != 0) + { + if ((e = collide(solidType, this.x, this.y + sign))) + { + moveCollideY(e); + break; + } + else + { + this.y += sign; + y -= sign; + } + } + } + else this.y += y; + } + } + else + { + this.x += x; + this.y += y; + } + } + + /** + * Moves the Entity to the position, retaining integer values for its x and y. + * @param x X position. + * @param y Y position. + * @param solidType An optional collision type to stop flush against upon collision. + * @param sweep If sweeping should be used (prevents fast-moving objects from going through solidType). + */ + public function moveTo(x:Number, y:Number, solidType:String = null, sweep:Boolean = false):void + { + moveBy(x - this.x, y - this.y, solidType, sweep); + } + + /** + * Moves towards the target position, retaining integer values for its x and y. + * @param x X target. + * @param y Y target. + * @param amount Amount to move. + * @param solidType An optional collision type to stop flush against upon collision. + * @param sweep If sweeping should be used (prevents fast-moving objects from going through solidType). + */ + public function moveTowards(x:Number, y:Number, amount:Number, solidType:String = null, sweep:Boolean = false):void + { + _point.x = x - this.x; + _point.y = y - this.y; + _point.normalize(amount); + moveBy(_point.x, _point.y, solidType, sweep); + } + + /** + * When you collide with an Entity on the x-axis with moveTo() or moveBy(). + * @param e The Entity you collided with. + */ + public function moveCollideX(e:Entity):void + { + + } + + /** + * When you collide with an Entity on the y-axis with moveTo() or moveBy(). + * @param e The Entity you collided with. + */ + public function moveCollideY(e:Entity):void + { + + } + + /** + * Clamps the Entity's hitbox on the x-axis. + * @param left Left bounds. + * @param right Right bounds. + * @param padding Optional padding on the clamp. + */ + public function clampHorizontal(left:Number, right:Number, padding:Number = 0):void + { + if (x - originX < left + padding) x = left + originX + padding; + if (x - originX + width > right - padding) x = right - width + originX - padding; + } + + /** + * Clamps the Entity's hitbox on the y axis. + * @param top Min bounds. + * @param bottom Max bounds. + * @param padding Optional padding on the clamp. + */ + public function clampVertical(top:Number, bottom:Number, padding:Number = 0):void + { + if (y - originY < top + padding) y = top + originY + padding; + if (y - originY + height > bottom - padding) y = bottom - height + originY - padding; + } + + // Entity information. + /** @private */ internal var _class:Class; + /** @private */ internal var _world:World; + /** @private */ internal var _added:Boolean; + /** @private */ internal var _type:String = ""; + /** @private */ internal var _layer:int; + /** @private */ internal var _updatePrev:Entity; + /** @private */ internal var _updateNext:Entity; + /** @private */ internal var _renderPrev:Entity; + /** @private */ internal var _renderNext:Entity; + /** @private */ internal var _typePrev:Entity; + /** @private */ internal var _typeNext:Entity; + /** @private */ internal var _recycleNext:Entity; + + // Collision information. + /** @private */ private const HITBOX:Mask = new Mask; + /** @private */ private var _mask:Mask; + /** @private */ private var _x:Number; + /** @private */ private var _y:Number; + /** @private */ private var _moveX:Number = 0; + /** @private */ private var _moveY:Number = 0; + + // Rendering information. + /** @private */ internal var _graphic:Graphic; + /** @private */ private var _point:Point = FP.point; + /** @private */ private var _camera:Point = FP.point2; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/FP.as b/src/net/flashpunk/FP.as new file mode 100644 index 0000000..af22d2b --- /dev/null +++ b/src/net/flashpunk/FP.as @@ -0,0 +1,886 @@ +package net.flashpunk +{ + import flash.display.BitmapData; + import flash.display.Sprite; + import flash.display.Stage; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.media.SoundMixer; + import flash.media.SoundTransform; + import flash.system.System; + import flash.utils.ByteArray; + import flash.utils.getTimer; + import net.flashpunk.*; + import net.flashpunk.debug.Console; + import net.flashpunk.tweens.misc.MultiVarTween; + + /** + * Static catch-all class used to access global properties and functions. + */ + public class FP + { + /** + * The FlashPunk major version. + */ + public static const VERSION:String = "1.4"; + + /** + * Width of the game. + */ + public static var width:uint; + + /** + * Height of the game. + */ + public static var height:uint; + + /** + * If the game is running at a fixed framerate. + */ + public static var fixed:Boolean; + + /** + * The framerate assigned to the stage. + */ + public static var frameRate:Number; + + /** + * The framerate assigned to the stage. + */ + public static var assignedFrameRate:Number; + + /** + * Time elapsed since the last frame (non-fixed framerate only). + */ + public static var elapsed:Number; + + /** + * Timescale applied to FP.elapsed (non-fixed framerate only). + */ + public static var rate:Number = 1; + + /** + * The Screen object, use to transform or offset the Screen. + */ + public static var screen:Screen; + + /** + * The current screen buffer, drawn to in the render loop. + */ + public static var buffer:BitmapData; + + /** + * A rectangle representing the size of the screen. + */ + public static var bounds:Rectangle; + + /** + * Point used to determine drawing offset in the render loop. + */ + public static var camera:Point = new Point; + + /** + * Half the screen width. + */ + public static function get halfWidth():Number { return width / 2; } + + /** + * Half the screen height. + */ + public static function get halfHeight():Number { return height / 2; } + + /** + * The currently active World object. When you set this, the World is flagged + * to switch, but won't actually do so until the end of the current frame. + */ + public static function get world():World { return _world; } + public static function set world(value:World):void + { + if (_world == value) return; + _goto = value; + } + + /** + * Sets the camera position. + * @param x X position. + * @param y Y position. + */ + public static function setCamera(x:Number = 0, y:Number = 0):void + { + camera.x = x; + camera.y = y; + } + + /** + * Resets the camera position. + */ + public static function resetCamera():void + { + camera.x = camera.y = 0; + } + + /** + * Global volume factor for all sounds, a value from 0 to 1. + */ + public static function get volume():Number { return _volume; } + public static function set volume(value:Number):void + { + if (value < 0) value = 0; + if (_volume == value) return; + _soundTransform.volume = _volume = value; + SoundMixer.soundTransform = _soundTransform; + } + + /** + * Global panning factor for all sounds, a value from -1 to 1. + */ + public static function get pan():Number { return _pan; } + public static function set pan(value:Number):void + { + if (value < -1) value = -1; + if (value > 1) value = 1; + if (_pan == value) return; + _soundTransform.pan = _pan = value; + SoundMixer.soundTransform = _soundTransform; + } + + /** + * Randomly chooses and returns one of the provided values. + * @param ...objs The Objects you want to randomly choose from. Can be ints, Numbers, Points, etc. + * @return A randomly chosen one of the provided parameters. + */ + public static function choose(...objs):* + { + var c:* = (objs.length == 1 && (objs[0] is Array || objs[0] is Vector.<*>)) ? objs[0] : objs; + return c[rand(c.length)]; + } + + /** + * Finds the sign of the provided value. + * @param value The Number to evaluate. + * @return 1 if value > 0, -1 if value < 0, and 0 when value == 0. + */ + public static function sign(value:Number):int + { + return value < 0 ? -1 : (value > 0 ? 1 : 0); + } + + /** + * Approaches the value towards the target, by the specified amount, without overshooting the target. + * @param value The starting value. + * @param target The target that you want value to approach. + * @param amount How much you want the value to approach target by. + * @return The new value. + */ + public static function approach(value:Number, target:Number, amount:Number):Number + { + return value < target ? (target < value + amount ? target : value + amount) : (target > value - amount ? target : value - amount); + } + + /** + * Linear interpolation between two values. + * @param a First value. + * @param b Second value. + * @param t Interpolation factor. + * @return When t=0, returns a. When t=1, returns b. When t=0.5, will return halfway between a and b. Etc. + */ + public static function lerp(a:Number, b:Number, t:Number = 1):Number + { + return a + (b - a) * t; + } + + /** + * Linear interpolation between two colors. + * @param fromColor First color. + * @param toColor Second color. + * @param t Interpolation value. Clamped to the range [0, 1]. + * return RGB component-interpolated color value. + */ + public static function colorLerp(fromColor:uint, toColor:uint, t:Number = 1):uint + { + if (t <= 0) { return fromColor; } + if (t >= 1) { return toColor; } + var a:uint = fromColor >> 24 & 0xFF, + r:uint = fromColor >> 16 & 0xFF, + g:uint = fromColor >> 8 & 0xFF, + b:uint = fromColor & 0xFF, + dA: int = (toColor >> 24 & 0xFF) - a, + dR: int = (toColor >> 16 & 0xFF) - r, + dG: int = (toColor >> 8 & 0xFF) - g, + dB: int = (toColor & 0xFF) - b; + a += dA * t; + r += dR * t; + g += dG * t; + b += dB * t; + return a << 24 | r << 16 | g << 8 | b; + } + + /** + * Steps the object towards a point. + * @param object Object to move (must have an x and y property). + * @param x X position to step towards. + * @param y Y position to step towards. + * @param distance The distance to step (will not overshoot target). + */ + public static function stepTowards(object:Object, x:Number, y:Number, distance:Number = 1):void + { + point.x = x - object.x; + point.y = y - object.y; + if (point.length <= distance) + { + object.x = x; + object.y = y; + return; + } + point.normalize(distance); + object.x += point.x; + object.y += point.y; + } + + /** + * Anchors the object to a position. + * @param object The object to anchor. + * @param anchor The anchor object. + * @param distance The max distance object can be anchored to the anchor. + */ + public static function anchorTo(object:Object, anchor:Object, distance:Number = 0):void + { + point.x = object.x - anchor.x; + point.y = object.y - anchor.y; + if (point.length > distance) point.normalize(distance); + object.x = anchor.x + point.x; + object.y = anchor.y + point.y; + } + + /** + * Finds the angle (in degrees) from point 1 to point 2. + * @param x1 The first x-position. + * @param y1 The first y-position. + * @param x2 The second x-position. + * @param y2 The second y-position. + * @return The angle from (x1, y1) to (x2, y2). + */ + public static function angle(x1:Number, y1:Number, x2:Number, y2:Number):Number + { + var a:Number = Math.atan2(y2 - y1, x2 - x1) * DEG; + return a < 0 ? a + 360 : a; + } + + /** + * Sets the x/y values of the provided object to a vector of the specified angle and length. + * @param object The object whose x/y properties should be set. + * @param angle The angle of the vector, in degrees. + * @param length The distance to the vector from (0, 0). + * @param x X offset. + * @param y Y offset. + */ + public static function angleXY(object:Object, angle:Number, length:Number = 1, x:Number = 0, y:Number = 0):void + { + angle *= RAD; + object.x = Math.cos(angle) * length + x; + object.y = Math.sin(angle) * length + y; + } + + /** + * Rotates the object around the anchor by the specified amount. + * @param object Object to rotate around the anchor. + * @param anchor Anchor to rotate around. + * @param angle The amount of degrees to rotate by. + */ + public static function rotateAround(object:Object, anchor:Object, angle:Number = 0, relative:Boolean = true):void + { + if (relative) angle += FP.angle(anchor.x, anchor.y, object.x, object.y); + FP.angleXY(object, angle, FP.distance(anchor.x, anchor.y, object.x, object.y), anchor.x, anchor.y); + } + + /** + * Find the distance between two points. + * @param x1 The first x-position. + * @param y1 The first y-position. + * @param x2 The second x-position. + * @param y2 The second y-position. + * @return The distance. + */ + public static function distance(x1:Number, y1:Number, x2:Number = 0, y2:Number = 0):Number + { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + } + + /** + * Find the distance between two rectangles. Will return 0 if the rectangles overlap. + * @param x1 The x-position of the first rect. + * @param y1 The y-position of the first rect. + * @param w1 The width of the first rect. + * @param h1 The height of the first rect. + * @param x2 The x-position of the second rect. + * @param y2 The y-position of the second rect. + * @param w2 The width of the second rect. + * @param h2 The height of the second rect. + * @return The distance. + */ + public static function distanceRects(x1:Number, y1:Number, w1:Number, h1:Number, x2:Number, y2:Number, w2:Number, h2:Number):Number + { + if (x1 < x2 + w2 && x2 < x1 + w1) + { + if (y1 < y2 + h2 && y2 < y1 + h1) return 0; + if (y1 > y2) return y1 - (y2 + h2); + return y2 - (y1 + h1); + } + if (y1 < y2 + h2 && y2 < y1 + h1) + { + if (x1 > x2) return x1 - (x2 + w2); + return x2 - (x1 + w1) + } + if (x1 > x2) + { + if (y1 > y2) return distance(x1, y1, (x2 + w2), (y2 + h2)); + return distance(x1, y1 + h1, x2 + w2, y2); + } + if (y1 > y2) return distance(x1 + w1, y1, x2, y2 + h2) + return distance(x1 + w1, y1 + h1, x2, y2); + } + + /** + * Find the distance between a point and a rectangle. Returns 0 if the point is within the rectangle. + * @param px The x-position of the point. + * @param py The y-position of the point. + * @param rx The x-position of the rect. + * @param ry The y-position of the rect. + * @param rw The width of the rect. + * @param rh The height of the rect. + * @return The distance. + */ + public static function distanceRectPoint(px:Number, py:Number, rx:Number, ry:Number, rw:Number, rh:Number):Number + { + if (px >= rx && px <= rx + rw) + { + if (py >= ry && py <= ry + rh) return 0; + if (py > ry) return py - (ry + rh); + return ry - py; + } + if (py >= ry && py <= ry + rh) + { + if (px > rx) return px - (rx + rw); + return rx - px; + } + if (px > rx) + { + if (py > ry) return distance(px, py, rx + rw, ry + rh); + return distance(px, py, rx + rw, ry); + } + if (py > ry) return distance(px, py, rx, ry + rh) + return distance(px, py, rx, ry); + } + + /** + * Clamps the value within the minimum and maximum values. + * @param value The Number to evaluate. + * @param min The minimum range. + * @param max The maximum range. + * @return The clamped value. + */ + public static function clamp(value:Number, min:Number, max:Number):Number + { + if (max > min) + { + value = value < max ? value : max; + return value > min ? value : min; + } + value = value < min ? value : min; + return value > max ? value : max; + } + + /** + * Clamps the object inside the rectangle. + * @param object The object to clamp (must have an x and y property). + * @param x Rectangle's x. + * @param y Rectangle's y. + * @param width Rectangle's width. + * @param height Rectangle's height. + */ + public static function clampInRect(object:Object, x:Number, y:Number, width:Number, height:Number, padding:Number = 0):void + { + object.x = clamp(object.x, x + padding, x + width - padding); + object.y = clamp(object.y, y + padding, y + height - padding); + } + + /** + * Transfers a value from one scale to another scale. For example, scale(.5, 0, 1, 10, 20) == 15, and scale(3, 0, 5, 100, 0) == 40. + * @param value The value on the first scale. + * @param min The minimum range of the first scale. + * @param max The maximum range of the first scale. + * @param min2 The minimum range of the second scale. + * @param max2 The maximum range of the second scale. + * @return The scaled value. + */ + public static function scale(value:Number, min:Number, max:Number, min2:Number, max2:Number):Number + { + return min2 + ((value - min) / (max - min)) * (max2 - min2); + } + + /** + * Transfers a value from one scale to another scale, but clamps the return value within the second scale. + * @param value The value on the first scale. + * @param min The minimum range of the first scale. + * @param max The maximum range of the first scale. + * @param min2 The minimum range of the second scale. + * @param max2 The maximum range of the second scale. + * @return The scaled and clamped value. + */ + public static function scaleClamp(value:Number, min:Number, max:Number, min2:Number, max2:Number):Number + { + value = min2 + ((value - min) / (max - min)) * (max2 - min2); + if (max2 > min2) + { + value = value < max2 ? value : max2; + return value > min2 ? value : min2; + } + value = value < min2 ? value : min2; + return value > max2 ? value : max2; + } + + /** + * The random seed used by FP's random functions. + */ + public static function get randomSeed():uint { return _getSeed; } + public static function set randomSeed(value:uint):void + { + _seed = clamp(value, 1, 2147483646); + _getSeed = _seed; + } + + /** + * Randomizes the random seed using Flash's Math.random() function. + */ + public static function randomizeSeed():void + { + randomSeed = 2147483647 * Math.random(); + } + + /** + * A pseudo-random Number produced using FP's random seed, where 0 <= Number < 1. + */ + public static function get random():Number + { + _seed = (_seed * 16807) % 2147483647; + return _seed / 2147483647; + } + + /** + * Returns a pseudo-random uint. + * @param amount The returned uint will always be 0 <= uint < amount. + * @return The uint. + */ + public static function rand(amount:uint):uint + { + _seed = (_seed * 16807) % 2147483647; + return (_seed / 2147483647) * amount; + } + + /** + * Returns the next item after current in the list of options. + * @param current The currently selected item (must be one of the options). + * @param options An array of all the items to cycle through. + * @param loop If true, will jump to the first item after the last item is reached. + * @return The next item in the list. + */ + public static function next(current:*, options:Array, loop:Boolean = true):* + { + if (loop) return options[(options.indexOf(current) + 1) % options.length]; + return options[Math.max(options.indexOf(current) + 1, options.length - 1)]; + } + + /** + * Returns the item previous to the current in the list of options. + * @param current The currently selected item (must be one of the options). + * @param options An array of all the items to cycle through. + * @param loop If true, will jump to the last item after the first is reached. + * @return The previous item in the list. + */ + public static function prev(current:*, options:Array, loop:Boolean = true):* + { + if (loop) return options[((options.indexOf(current) - 1) + options.length) % options.length]; + return options[Math.max(options.indexOf(current) - 1, 0)]; + } + + /** + * Swaps the current item between a and b. Useful for quick state/string/value swapping. + * @param current The currently selected item. + * @param a Item a. + * @param b Item b. + * @return Returns a if current is b, and b if current is a. + */ + public static function swap(current:*, a:*, b:*):* + { + return current == a ? b : a; + } + + /** + * Creates a color value by combining the chosen RGB values. + * @param R The red value of the color, from 0 to 255. + * @param G The green value of the color, from 0 to 255. + * @param B The blue value of the color, from 0 to 255. + * @return The color uint. + */ + public static function getColorRGB(R:uint = 0, G:uint = 0, B:uint = 0):uint + { + return R << 16 | G << 8 | B; + } + + /** + * Creates a color value with the chosen HSV values. + * @param h The hue of the color (from 0 to 1). + * @param s The saturation of the color (from 0 to 1). + * @param v The value of the color (from 0 to 1). + * @return The color uint. + */ + public static function getColorHSV(h:Number, s:Number, v:Number):uint + { + h = int(h * 360); + var hi:int = Math.floor(h / 60) % 6, + f:Number = h / 60 - Math.floor(h / 60), + p:Number = (v * (1 - s)), + q:Number = (v * (1 - f * s)), + t:Number = (v * (1 - (1 - f) * s)); + switch (hi) + { + case 0: return int(v * 255) << 16 | int(t * 255) << 8 | int(p * 255); + case 1: return int(q * 255) << 16 | int(v * 255) << 8 | int(p * 255); + case 2: return int(p * 255) << 16 | int(v * 255) << 8 | int(t * 255); + case 3: return int(p * 255) << 16 | int(q * 255) << 8 | int(v * 255); + case 4: return int(t * 255) << 16 | int(p * 255) << 8 | int(v * 255); + case 5: return int(v * 255) << 16 | int(p * 255) << 8 | int(q * 255); + default: return 0; + } + return 0; + } + + /** + * Finds the red factor of a color. + * @param color The color to evaluate. + * @return A uint from 0 to 255. + */ + public static function getRed(color:uint):uint + { + return color >> 16 & 0xFF; + } + + /** + * Finds the green factor of a color. + * @param color The color to evaluate. + * @return A uint from 0 to 255. + */ + public static function getGreen(color:uint):uint + { + return color >> 8 & 0xFF; + } + + /** + * Finds the blue factor of a color. + * @param color The color to evaluate. + * @return A uint from 0 to 255. + */ + public static function getBlue(color:uint):uint + { + return color & 0xFF; + } + + /** + * Fetches a stored BitmapData object represented by the source. + * @param source Embedded Bitmap class. + * @return The stored BitmapData object. + */ + public static function getBitmap(source:Class):BitmapData + { + if (_bitmap[String(source)]) return _bitmap[String(source)]; + return (_bitmap[String(source)] = (new source).bitmapData); + } + + /** + * Sets a time flag. + * @return Time elapsed (in milliseconds) since the last time flag was set. + */ + public static function timeFlag():uint + { + var t:uint = getTimer(), + e:uint = t - _time; + _time = t; + return e; + } + + /** + * The global Console object. + */ + public static function get console():Console + { + if (!_console) _console = new Console; + return _console; + } + + /** + * Logs data to the console. + * @param ...data The data parameters to log, can be variables, objects, etc. Parameters will be separated by a space (" "). + */ + public static function log(...data):void + { + if (_console) + { + if (data.length > 1) + { + var i:int = 0, s:String = ""; + while (i < data.length) + { + if (i > 0) s += " "; + s += data[i ++].toString(); + } + _console.log(s); + } + else _console.log(data[0]); + } + } + + /** + * Adds properties to watch in the console's debug panel. + * @param ...properties The properties (strings) to watch. + */ + public static function watch(...properties):void + { + if (_console) + { + if (properties.length > 1) _console.watch(properties); + else _console.watch(properties[0]); + } + } + + /** + * Loads the file as an XML object. + * @param file The embedded file to load. + * @return An XML object representing the file. + */ + public static function getXML(file:Class):XML + { + var bytes:ByteArray = new file; + return XML(bytes.readUTFBytes(bytes.length)); + } + + /** + * Tweens numeric public properties of an Object. Shorthand for creating a MultiVarTween tween, starting it and adding it to a Tweener. + * @param object The object containing the properties to tween. + * @param values An object containing key/value pairs of properties and target values. + * @param duration Duration of the tween. + * @param options An object containing key/value pairs of the following optional parameters: + * type Tween type. + * complete Optional completion callback function. + * ease Optional easer function. + * tweener The Tweener to add this Tween to. + * @return The added MultiVarTween object. + * + * Example: FP.tween(object, { x: 500, y: 350 }, 2.0, { ease: easeFunction, complete: onComplete } ); + */ + public static function tween(object:Object, values:Object, duration:Number, options:Object = null):MultiVarTween + { + var type:uint = Tween.ONESHOT, + complete:Function = null, + ease:Function = null, + tweener:Tweener = FP.world; + if (object is Tweener) tweener = object as Tweener; + if (options) + { + if (options.hasOwnProperty("type")) type = options.type; + if (options.hasOwnProperty("complete")) complete = options.complete; + if (options.hasOwnProperty("ease")) ease = options.ease; + if (options.hasOwnProperty("tweener")) tweener = options.tweener; + } + var tween:MultiVarTween = new MultiVarTween(complete, type); + tween.tween(object, values, duration, ease); + tweener.addTween(tween); + return tween; + } + + /** + * Gets an array of frame indices. + * @param from Starting frame. + * @param to Ending frame. + * @param skip Skip amount every frame (eg. use 1 for every 2nd frame). + */ + public static function frames(from:int, to:int, skip:int = 0):Array + { + var a:Array = []; + skip ++; + if (from < to) + { + while (from <= to) + { + a.push(from); + from += skip; + } + } + else + { + while (from >= to) + { + a.push(from); + from -= skip; + } + } + return a; + } + + /** + * Shuffles the elements in the array. + * @param a The Object to shuffle (an Array or Vector). + */ + public static function shuffle(a:Object):void + { + if (a is Array || a is Vector.<*>) + { + var i:int = a.length, j:int, t:*; + while (-- i) + { + t = a[i]; + a[i] = a[j = FP.rand(i + 1)]; + a[j] = t; + } + } + } + + /** + * Sorts the elements in the array. + * @param object The Object to sort (an Array or Vector). + * @param ascending If it should be sorted ascending (true) or descending (false). + */ + public static function sort(object:Object, ascending:Boolean = true):void + { + if (object is Array || object is Vector.<*>) quicksort(object, 0, object.length - 1, ascending); + } + + /** + * Sorts the elements in the array by a property of the element. + * @param object The Object to sort (an Array or Vector). + * @param property The numeric property of object's elements to sort by. + * @param ascending If it should be sorted ascending (true) or descending (false). + */ + public static function sortBy(object:Object, property:String, ascending:Boolean = true):void + { + if (object is Array || object is Vector.<*>) quicksortBy(object, 0, object.length - 1, ascending, property); + } + + /** @private Quicksorts the array. */ + private static function quicksort(a:Object, left:int, right:int, ascending:Boolean):void + { + var i:int = left, j:int = right, t:Number, + p:* = a[Math.round((left + right) * .5)]; + if (ascending) + { + while (i <= j) + { + while (a[i] < p) i ++; + while (a[j] > p) j --; + if (i <= j) + { + t = a[i]; + a[i ++] = a[j]; + a[j --] = t; + } + } + } + else + { + while (i <= j) + { + while (a[i] > p) i ++; + while (a[j] < p) j --; + if (i <= j) + { + t = a[i]; + a[i ++] = a[j]; + a[j --] = t; + } + } + } + if (left < j) quicksort(a, left, j, ascending); + if (i < right) quicksort(a, i, right, ascending); + } + + /** @private Quicksorts the array by the property. */ + private static function quicksortBy(a:Object, left:int, right:int, ascending:Boolean, property:String):void + { + var i:int = left, j:int = right, t:Object, + p:* = a[Math.round((left + right) * .5)][property]; + if (ascending) + { + while (i <= j) + { + while (a[i][property] < p) i ++; + while (a[j][property] > p) j --; + if (i <= j) + { + t = a[i]; + a[i ++] = a[j]; + a[j --] = t; + } + } + } + else + { + while (i <= j) + { + while (a[i][property] > p) i ++; + while (a[j][property] < p) j --; + if (i <= j) + { + t = a[i]; + a[i ++] = a[j]; + a[j --] = t; + } + } + } + if (left < j) quicksortBy(a, left, j, ascending, property); + if (i < right) quicksortBy(a, i, right, ascending, property); + } + + // World information. + /** @private */ internal static var _world:World; + /** @private */ internal static var _goto:World; + + // Console information. + /** @private */ internal static var _console:Console; + + // Time information. + /** @private */ internal static var _time:uint; + /** @private */ public static var _updateTime:uint; + /** @private */ public static var _renderTime:uint; + /** @private */ public static var _gameTime:uint; + /** @private */ public static var _flashTime:uint; + + // Bitmap storage. + /** @private */ private static var _bitmap:Object = { }; + + // Pseudo-random number generation (the seed is set in Engine's contructor). + /** @private */ private static var _seed:uint = 0; + /** @private */ private static var _getSeed:uint; + + // Volume control. + /** @private */ private static var _volume:Number = 1; + /** @private */ private static var _pan:Number = 0; + /** @private */ private static var _soundTransform:SoundTransform = new SoundTransform; + + // Used for rad-to-deg and deg-to-rad conversion. + /** @private */ public static const DEG:Number = -180 / Math.PI; + /** @private */ public static const RAD:Number = Math.PI / -180; + + // Global Flash objects. + /** @private */ public static var stage:Stage; + /** @private */ public static var engine:Engine; + + // Global objects used for rendering, collision, etc. + /** @private */ public static var point:Point = new Point; + /** @private */ public static var point2:Point = new Point; + /** @private */ public static var zero:Point = new Point; + /** @private */ public static var rect:Rectangle = new Rectangle; + /** @private */ public static var matrix:Matrix = new Matrix; + /** @private */ public static var sprite:Sprite = new Sprite; + /** @private */ public static var entity:Entity; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Graphic.as b/src/net/flashpunk/Graphic.as new file mode 100644 index 0000000..18a38c3 --- /dev/null +++ b/src/net/flashpunk/Graphic.as @@ -0,0 +1,85 @@ +package net.flashpunk +{ + import flash.display.BitmapData; + import flash.geom.Point; + + /** + * Base class for all graphical types that can be drawn by Entity. + */ + public class Graphic + { + /** + * If the graphic should update. + */ + public var active:Boolean = false; + + /** + * If the graphic should render. + */ + public var visible:Boolean = true; + + /** + * X offset. + */ + public var x:Number = 0; + + /** + * Y offset. + */ + public var y:Number = 0; + + /** + * X scrollfactor, effects how much the camera offsets the drawn graphic. + * Can be used for parallax effect, eg. Set to 0 to follow the camera, + * 0.5 to move at half-speed of the camera, or 1 (default) to stay still. + */ + public var scrollX:Number = 1; + + /** + * Y scrollfactor, effects how much the camera offsets the drawn graphic. + * Can be used for parallax effect, eg. Set to 0 to follow the camera, + * 0.5 to move at half-speed of the camera, or 1 (default) to stay still. + */ + public var scrollY:Number = 1; + + /** + * If the graphic should render at its position relative to its parent Entity's position. + */ + public var relative:Boolean = true; + + /** + * Constructor. + */ + public function Graphic() + { + + } + + /** + * Updates the graphic. + */ + public function update():void + { + + } + + /** + * Renders the graphic to the screen buffer. + * @param point The position to draw the graphic. + * @param camera The camera offset. + */ + public function render(target:BitmapData, point:Point, camera:Point):void + { + + } + + /** @private Callback for when the graphic is assigned to an Entity. */ + protected function get assign():Function { return _assign; } + protected function set assign(value:Function):void { _assign = value; } + + // Graphic information. + /** @private */ internal var _assign:Function; + /** @private */ internal var _scroll:Boolean = true; + /** @private */ protected var _point:Point = new Point; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Mask.as b/src/net/flashpunk/Mask.as new file mode 100644 index 0000000..8edaaf7 --- /dev/null +++ b/src/net/flashpunk/Mask.as @@ -0,0 +1,78 @@ +package net.flashpunk +{ + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import net.flashpunk.masks.Hitbox; + import net.flashpunk.masks.Masklist; + + /** + * Base class for Entity collision masks. + */ + public class Mask + { + /** + * The parent Entity of this mask. + */ + public var parent:Entity; + + /** + * The parent Masklist of the mask. + */ + public var list:Masklist; + + /** + * Constructor. + */ + public function Mask() + { + _class = Class(getDefinitionByName(getQualifiedClassName(this))); + _check[Mask] = collideMask; + _check[Masklist] = collideMasklist; + } + + /** + * Checks for collision with another Mask. + * @param mask The other Mask to check against. + * @return If the Masks overlap. + */ + public function collide(mask:Mask):Boolean + { + if (_check[mask._class] != null) return _check[mask._class](mask); + if (mask._check[_class] != null) return mask._check[_class](this); + return false; + } + + /** @private Collide against an Entity. */ + private function collideMask(other:Mask):Boolean + { + return parent.x - parent.originX + parent.width > other.parent.x - other.parent.originX + && parent.y - parent.originY + parent.height > other.parent.y - other.parent.originY + && parent.x - parent.originX < other.parent.x - other.parent.originX + other.parent.width + && parent.y - parent.originY < other.parent.y - other.parent.originY + other.parent.height; + } + + /** @private Collide against a Masklist. */ + protected function collideMasklist(other:Masklist):Boolean + { + return other.collide(this); + } + + /** @private Assigns the mask to the parent. */ + internal function assignTo(parent:Entity):void + { + this.parent = parent; + if (parent) update(); + } + + /** @private Updates the parent's bounds for this mask. */ + protected function update():void + { + + } + + // Mask information. + /** @private */ private var _class:Class; + /** @private */ protected var _check:Dictionary = new Dictionary; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Screen.as b/src/net/flashpunk/Screen.as new file mode 100644 index 0000000..e01bb41 --- /dev/null +++ b/src/net/flashpunk/Screen.as @@ -0,0 +1,223 @@ +package net.flashpunk +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.PixelSnapping; + import flash.display.Sprite; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Transform; + import net.flashpunk.graphics.Image; + + /** + * Container for the main screen buffer. Can be used to transform the screen. + */ + public class Screen + { + /** + * Constructor. + */ + public function Screen() + { + // create screen buffers + _bitmap[0] = new Bitmap(new BitmapData(FP.width, FP.height, false, 0), PixelSnapping.NEVER); + _bitmap[1] = new Bitmap(new BitmapData(FP.width, FP.height, false, 0), PixelSnapping.NEVER); + FP.engine.addChild(_sprite); + _sprite.addChild(_bitmap[0]).visible = true; + _sprite.addChild(_bitmap[1]).visible = false; + FP.buffer = _bitmap[0].bitmapData; + _width = FP.width; + _height = FP.height; + update(); + } + + /** + * Swaps screen buffers. + */ + public function swap():void + { + _current = 1 - _current; + FP.buffer = _bitmap[_current].bitmapData; + } + + /** + * Refreshes the screen. + */ + public function refresh():void + { + // refreshes the screen + FP.buffer.fillRect(FP.bounds, _color); + } + + /** + * Redraws the screen. + */ + public function redraw():void + { + // refresh the buffers + _bitmap[_current].visible = true; + _bitmap[1 - _current].visible = false; + } + + /** @private Re-applies transformation matrix. */ + public function update():void + { + _matrix.b = _matrix.c = 0; + _matrix.a = _scaleX * _scale; + _matrix.d = _scaleY * _scale; + _matrix.tx = -_originX * _matrix.a; + _matrix.ty = -_originY * _matrix.d; + if (_angle != 0) _matrix.rotate(_angle); + _matrix.tx += _originX * _scaleX * _scale + _x; + _matrix.ty += _originY * _scaleX * _scale + _y; + _sprite.transform.matrix = _matrix; + } + + /** + * Refresh color of the screen. + */ + public function get color():uint { return _color; } + public function set color(value:uint):void { _color = 0xFF000000 | value; } + + /** + * X offset of the screen. + */ + public function get x():int { return _x; } + public function set x(value:int):void + { + if (_x == value) return; + _x = value; + update(); + } + + /** + * Y offset of the screen. + */ + public function get y():int { return _y; } + public function set y(value:int):void + { + if (_y == value) return; + _y = value; + update(); + } + + /** + * X origin of transformations. + */ + public function get originX():int { return _originX; } + public function set originX(value:int):void + { + if (_originX == value) return; + _originX = value; + update(); + } + + /** + * Y origin of transformations. + */ + public function get originY():int { return _originY; } + public function set originY(value:int):void + { + if (_originY == value) return; + _originY = value; + update(); + } + + /** + * X scale of the screen. + */ + public function get scaleX():Number { return _scaleX; } + public function set scaleX(value:Number):void + { + if (_scaleX == value) return; + _scaleX = value; + update(); + } + + /** + * Y scale of the screen. + */ + public function get scaleY():Number { return _scaleY; } + public function set scaleY(value:Number):void + { + if (_scaleY == value) return; + _scaleY = value; + update(); + } + + /** + * Scale factor of the screen. Final scale is scaleX * scale by scaleY * scale, so + * you can use this factor to scale the screen both horizontally and vertically. + */ + public function get scale():Number { return _scale; } + public function set scale(value:Number):void + { + if (_scale == value) return; + _scale = value; + update(); + } + + /** + * Rotation of the screen, in degrees. + */ + public function get angle():Number { return _angle * FP.DEG; } + public function set angle(value:Number):void + { + if (_angle == value * FP.RAD) return; + _angle = value * FP.RAD; + update(); + } + + /** + * Whether screen smoothing should be used or not. + */ + public function get smoothing():Boolean { return _bitmap[0].smoothing; } + public function set smoothing(value:Boolean):void { _bitmap[0].smoothing = _bitmap[1].smoothing = value; } + + /** + * Width of the screen. + */ + public function get width():uint { return _width; } + + /** + * Height of the screen. + */ + public function get height():uint { return _height; } + + /** + * X position of the mouse on the screen. + */ + public function get mouseX():int { return (FP.stage.mouseX - _x) / (_scaleX * _scale); } + + /** + * Y position of the mouse on the screen. + */ + public function get mouseY():int { return (FP.stage.mouseY - _y) / (_scaleY * _scale); } + + /** + * Captures the current screen as an Image object. + * @return A new Image object. + */ + public function capture():Image + { + return new Image(_bitmap[_current].bitmapData.clone()); + } + + // Screen infromation. + /** @private */ private var _sprite:Sprite = new Sprite; + /** @private */ private var _bitmap:Vector. = new Vector.(2); + /** @private */ private var _current:int = 0; + /** @private */ private var _matrix:Matrix = new Matrix; + /** @private */ private var _x:int; + /** @private */ private var _y:int; + /** @private */ private var _width:uint; + /** @private */ private var _height:uint; + /** @private */ private var _originX:int; + /** @private */ private var _originY:int; + /** @private */ private var _scaleX:Number = 1; + /** @private */ private var _scaleY:Number = 1; + /** @private */ private var _scale:Number = 1; + /** @private */ private var _angle:Number = 0; + /** @private */ private var _color:uint = 0x202020; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Sfx.as b/src/net/flashpunk/Sfx.as new file mode 100644 index 0000000..f2f2d16 --- /dev/null +++ b/src/net/flashpunk/Sfx.as @@ -0,0 +1,144 @@ +package net.flashpunk +{ + import flash.events.Event; + import flash.media.Sound; + import flash.media.SoundChannel; + import flash.media.SoundTransform; + import flash.utils.Dictionary; + + /** + * Sound effect object used to play embedded sounds. + */ + public class Sfx + { + /** + * Optional callback function for when the sound finishes playing. + */ + public var complete:Function; + + /** + * Creates a sound effect from an embedded source. Store a reference to + * this object so that you can play the sound using play() or loop(). + * @param source The embedded sound class to use. + * @param complete Optional callback function for when the sound finishes playing. + */ + public function Sfx(source:Class, complete:Function = null) + { + _sound = _sounds[source]; + if (!_sound) _sound = _sounds[source] = new source; + this.complete = complete; + } + + /** + * Plays the sound once. + * @param vol Volume factor, a value from 0 to 1. + * @param pan Panning factor, a value from -1 to 1. + */ + public function play(vol:Number = 1, pan:Number = 0):void + { + if (_channel) stop(); + _vol = _transform.volume = vol < 0 ? 0 : vol; + _pan = _transform.pan = pan < -1 ? -1 : (pan > 1 ? 1 : pan); + _channel = _sound.play(0, 0, _transform); + _channel.addEventListener(Event.SOUND_COMPLETE, onComplete); + _looping = false; + _position = 0; + } + + /** + * Plays the sound looping. Will loop continuously until you call stop(), play(), or loop() again. + * @param vol Volume factor, a value from 0 to 1. + * @param pan Panning factor, a value from -1 to 1. + */ + public function loop(vol:Number = 1, pan:Number = 0):void + { + play(vol, pan); + _looping = true; + } + + /** + * Stops the sound if it is currently playing. + * @return + */ + public function stop():Boolean + { + if (!_channel) return false; + _position = _channel.position; + _channel.removeEventListener(Event.SOUND_COMPLETE, onComplete); + _channel.stop(); + _channel = null; + return true; + } + + /** + * Resumes the sound from the position stop() was called on it. + */ + public function resume():void + { + _channel = _sound.play(_position, 0, _transform); + _channel.addEventListener(Event.SOUND_COMPLETE, onComplete); + _position = 0; + } + + /** @private Event handler for sound completion. */ + private function onComplete(e:Event = null):void + { + if (_looping) loop(_vol, _pan); + else stop(); + _position = 0; + if (complete != null) complete(); + } + + /** + * Alter the volume factor (a value from 0 to 1) of the sound during playback. + */ + public function get volume():Number { return _vol; } + public function set volume(value:Number):void + { + if (value < 0) value = 0; + if (!_channel || _vol == value) return; + _vol = _transform.volume = value; + _channel.soundTransform = _transform; + } + + /** + * Alter the panning factor (a value from -1 to 1) of the sound during playback. + */ + public function get pan():Number { return _pan; } + public function set pan(value:Number):void + { + if (value < -1) value = -1; + if (value > 1) value = 1; + if (!_channel || _pan == value) return; + _pan = _transform.pan = value; + _channel.soundTransform = _transform; + } + + /** + * If the sound is currently playing. + */ + public function get playing():Boolean { return _channel != null; } + + /** + * Position of the currently playing sound, in seconds. + */ + public function get position():Number { return (_channel ? _channel.position : _position) / 1000; } + + /** + * Length of the sound, in seconds. + */ + public function get length():Number { return _sound.length / 1000; } + + // Sound infromation. + /** @private */ private var _vol:Number = 1; + /** @private */ private var _pan:Number = 0; + /** @private */ private var _sound:Sound; + /** @private */ private var _channel:SoundChannel; + /** @private */ private var _transform:SoundTransform = new SoundTransform; + /** @private */ private var _position:Number = 0; + /** @private */ private var _looping:Boolean; + + // Stored Sound objects. + /** @private */ private static var _sounds:Dictionary = new Dictionary; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Tween.as b/src/net/flashpunk/Tween.as new file mode 100644 index 0000000..4b04d96 --- /dev/null +++ b/src/net/flashpunk/Tween.as @@ -0,0 +1,128 @@ +package net.flashpunk +{ + /** + * Base class for all Tween objects, can be added to any Core-extended classes. + */ + public class Tween + { + /** + * Persistent Tween type, will stop when it finishes. + */ + public static const PERSIST:uint = 0; + + /** + * Looping Tween type, will restart immediately when it finishes. + */ + public static const LOOPING:uint = 1; + + /** + * Oneshot Tween type, will stop and remove itself from its core container when it finishes. + */ + public static const ONESHOT:uint = 2; + + /** + * If the tween should update. + */ + public var active:Boolean; + + /** + * Tween completion callback. + */ + public var complete:Function; + + /** + * Constructor. Specify basic information about the Tween. + * @param duration Duration of the tween (in seconds or frames). + * @param type Tween type, one of Tween.PERSIST (default), Tween.LOOPING, or Tween.ONESHOT. + * @param complete Optional callback for when the Tween completes. + * @param ease Optional easer function to apply to the Tweened value. + */ + public function Tween(duration:Number, type:uint = 0, complete:Function = null, ease:Function = null) + { + _target = duration; + _type = type; + this.complete = complete; + _ease = ease; + } + + /** + * Updates the Tween, called by World. + */ + public function update():void + { + _time += FP.fixed ? 1 : FP.elapsed; + _t = _time / _target; + if (_ease != null && _t > 0 && _t < 1) _t = _ease(_t); + if (_time >= _target) + { + _t = 1; + _finish = true; + } + } + + /** + * Starts the Tween, or restarts it if it's currently running. + */ + public function start():void + { + _time = 0; + if (_target == 0) + { + active = false; + return; + } + active = true; + } + + /** @private Called when the Tween completes. */ + internal function finish():void + { + switch (_type) + { + case 0: + _time = _target; + active = false; + break; + case 1: + _time %= _target; + _t = _time / _target; + if (_ease != null && _t > 0 && _t < 1) _t = _ease(_t); + start(); + break; + case 2: + _time = _target; + active = false; + _parent.removeTween(this); + break; + } + _finish = false; + if (complete != null) complete(); + } + + /** + * The completion percentage of the Tween. + */ + public function get percent():Number { return _time / _target; } + public function set percent(value:Number):void { _time = _target * value; } + + /** + * The current time scale of the Tween (after easer has been applied). + */ + public function get scale():Number { return _t; } + + // Tween information. + /** @private */ private var _type:uint; + /** @private */ protected var _ease:Function; + /** @private */ protected var _t:Number = 0; + + // Timing information. + /** @private */ protected var _time:Number; + /** @private */ protected var _target:Number; + + // List information. + /** @private */ internal var _finish:Boolean; + /** @private */ internal var _parent:Tweener; + /** @private */ internal var _prev:Tween; + /** @private */ internal var _next:Tween; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/Tweener.as b/src/net/flashpunk/Tweener.as new file mode 100644 index 0000000..e318382 --- /dev/null +++ b/src/net/flashpunk/Tweener.as @@ -0,0 +1,119 @@ +package net.flashpunk +{ + /** + * Updateable Tween container. + */ + public class Tweener + { + /** + * Persistent Tween type, will stop when it finishes. + */ + public const PERSIST:uint = 0; + + /** + * Looping Tween type, will restart immediately when it finishes. + */ + public const LOOPING:uint = 1; + + /** + * Oneshot Tween type, will stop and remove itself from its core container when it finishes. + */ + public const ONESHOT:uint = 2; + + /** + * If the Tweener should update. + */ + public var active:Boolean = true; + + /** + * If the Tweener should clear on removal. For Entities, this is when they are + * removed from a World, and for World this is when the active World is switched. + */ + public var autoClear:Boolean = false; + + /** + * Constructor. + */ + public function Tweener() + { + + } + + /** + * Updates the Tween container. + */ + public function update():void + { + + } + + /** + * Adds a new Tween. + * @param t The Tween to add. + * @param start If the Tween should call start() immediately. + * @return The added Tween. + */ + public function addTween(t:Tween, start:Boolean = false):Tween + { + if (t._parent) throw new Error("Cannot add a Tween object more than once."); + t._parent = this; + t._next = _tween; + if (_tween) _tween._prev = t; + _tween = t; + if (start) _tween.start(); + return t; + } + + /** + * Removes a Tween. + * @param t The Tween to remove. + * @return The removed Tween. + */ + public function removeTween(t:Tween):Tween + { + if (t._parent != this) throw new Error("Core object does not contain Tween."); + if (t._next) t._next._prev = t._prev; + if (t._prev) t._prev._next = t._next; + else _tween = t._next; + t._next = t._prev = null; + t._parent = null; + t.active = false; + return t; + } + + /** + * Removes all Tweens. + */ + public function clearTweens():void + { + var t:Tween = _tween, + n:Tween; + while (t) + { + n = t._next; + removeTween(t); + t = n; + } + } + + /** + * Updates all contained tweens. + */ + public function updateTweens():void + { + var t:Tween = _tween; + while (t) + { + if (t.active) + { + t.update(); + if (t._finish) t.finish(); + } + t = t._next; + } + } + + // List information. + /** @private */ internal var _tween:Tween; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/World.as b/src/net/flashpunk/World.as new file mode 100644 index 0000000..ff3c8a5 --- /dev/null +++ b/src/net/flashpunk/World.as @@ -0,0 +1,1128 @@ +package net.flashpunk +{ + import flash.geom.Point; + import flash.utils.Dictionary; + import net.flashpunk.utils.Input; + + /** + * Updated by Engine, main game container that holds all currently active Entities. + * Useful for organization, eg. "Menu", "Level1", etc. + */ + public class World extends Tweener + { + /** + * If the render() loop is performed. + */ + public var visible:Boolean = true; + + /** + * Point used to determine drawing offset in the render loop. + */ + public var camera:Point = new Point; + + /** + * Constructor. + */ + public function World() + { + + } + + /** + * Override this; called when World is switch to, and set to the currently active world. + */ + public function begin():void + { + + } + + /** + * Override this; called when World is changed, and the active world is no longer this. + */ + public function end():void + { + + } + + /** + * Performed by the game loop, updates all contained Entities. + * If you override this to give your World update code, remember + * to call super.update() or your Entities will not be updated. + */ + override public function update():void + { + // update the entities + var e:Entity = _updateFirst; + while (e) + { + if (e.active) + { + if (e._tween) e.updateTweens(); + e.update(); + } + if (e._graphic && e._graphic.active) e._graphic.update(); + e = e._updateNext; + } + } + + /** + * Performed by the game loop, renders all contained Entities. + * If you override this to give your World render code, remember + * to call super.render() or your Entities will not be rendered. + */ + public function render():void + { + // render the entities in order of depth + var e:Entity, + i:int = _layerList.length; + while (i --) + { + e = _renderLast[_layerList[i]]; + while (e) + { + if (e.visible) e.render(); + e = e._renderPrev; + } + } + } + + /** + * X position of the mouse in the World. + */ + public function get mouseX():int + { + return FP.screen.mouseX + FP.camera.x; + } + + /** + * Y position of the mouse in the world. + */ + public function get mouseY():int + { + return FP.screen.mouseY + FP.camera.y; + } + + /** + * Adds the Entity to the World at the end of the frame. + * @param e Entity object you want to add. + * @return The added Entity object. + */ + public function add(e:Entity):Entity + { + if (e._world) return e; + _add[_add.length] = e; + e._world = this; + return e; + } + + /** + * Removes the Entity from the World at the end of the frame. + * @param e Entity object you want to remove. + * @return The removed Entity object. + */ + public function remove(e:Entity):Entity + { + if (e._world !== this) return e; + _remove[_remove.length] = e; + e._world = null; + return e; + } + + /** + * Removes all Entities from the World at the end of the frame. + */ + public function removeAll():void + { + var e:Entity = _updateFirst; + while (e) + { + _remove[_remove.length] = e; + e._world = null; + e = e._updateNext; + } + } + + /** + * Adds multiple Entities to the world. + * @param ...list Several Entities (as arguments) or an Array/Vector of Entities. + */ + public function addList(...list):void + { + var e:Entity; + if (list[0] is Array || list[0] is Vector.<*>) + { + for each (e in list[0]) add(e); + return; + } + for each (e in list) add(e); + } + + /** + * Removes multiple Entities from the world. + * @param ...list Several Entities (as arguments) or an Array/Vector of Entities. + */ + public function removeList(...list):void + { + var e:Entity; + if (list[0] is Array || list[0] is Vector.<*>) + { + for each (e in list[0]) remove(e); + return; + } + for each (e in list) remove(e); + } + + /** + * Adds an Entity to the World with the Graphic object. + * @param graphic Graphic to assign the Entity. + * @param x X position of the Entity. + * @param y Y position of the Entity. + * @param layer Layer of the Entity. + * @return The Entity that was added. + */ + public function addGraphic(graphic:Graphic, layer:int = 0, x:int = 0, y:int = 0):Entity + { + var e:Entity = new Entity(x, y, graphic); + if (layer != 0) e.layer = layer; + e.active = false; + return add(e); + } + + /** + * Adds an Entity to the World with the Mask object. + * @param mask Mask to assign the Entity. + * @param type Collision type of the Entity. + * @param x X position of the Entity. + * @param y Y position of the Entity. + * @return The Entity that was added. + */ + public function addMask(mask:Mask, type:String, x:int = 0, y:int = 0):Entity + { + var e:Entity = new Entity(x, y, null, mask); + if (type) e.type = type; + e.active = e.visible = false; + return add(e); + } + + /** + * Returns a new Entity, or a stored recycled Entity if one exists. + * @param classType The Class of the Entity you want to add. + * @param addToWorld Add it to the World immediately. + * @return The new Entity object. + */ + public function create(classType:Class, addToWorld:Boolean = true):Entity + { + var e:Entity = _recycled[classType]; + if (e) + { + _recycled[classType] = e._recycleNext; + e._recycleNext = null; + } + else e = new classType; + if (addToWorld) return add(e); + return e; + } + + /** + * Removes the Entity from the World at the end of the frame and recycles it. + * The recycled Entity can then be fetched again by calling the create() function. + * @param e The Entity to recycle. + * @return The recycled Entity. + */ + public function recycle(e:Entity):Entity + { + if (e._world !== this) return e; + e._recycleNext = _recycled[e._class]; + _recycled[e._class] = e; + return remove(e); + } + + /** + * Clears stored reycled Entities of the Class type. + * @param classType The Class type to clear. + */ + public function clearRecycled(classType:Class):void + { + var e:Entity = _recycled[classType], + n:Entity; + while (e) + { + n = e._recycleNext; + e._recycleNext = null; + e = n; + } + delete _recycled[classType]; + } + + /** + * Clears stored recycled Entities of all Class types. + */ + public function clearRecycledAll():void + { + for (var classType:Object in _recycled) clearRecycled(classType as Class); + } + + /** + * Brings the Entity to the front of its contained layer. + * @param e The Entity to shift. + * @return If the Entity changed position. + */ + public function bringToFront(e:Entity):Boolean + { + if (e._world !== this || !e._renderPrev) return false; + // pull from list + e._renderPrev._renderNext = e._renderNext; + if (e._renderNext) e._renderNext._renderPrev = e._renderPrev; + else _renderLast[e._layer] = e._renderPrev; + // place at the start + e._renderNext = _renderFirst[e._layer]; + e._renderNext._renderPrev = e; + _renderFirst[e._layer] = e; + e._renderPrev = null; + return true; + } + + /** + * Sends the Entity to the back of its contained layer. + * @param e The Entity to shift. + * @return If the Entity changed position. + */ + public function sendToBack(e:Entity):Boolean + { + if (e._world !== this || !e._renderNext) return false; + // pull from list + e._renderNext._renderPrev = e._renderPrev; + if (e._renderPrev) e._renderPrev._renderNext = e._renderNext; + else _renderFirst[e._layer] = e._renderNext; + // place at the end + e._renderPrev = _renderLast[e._layer]; + e._renderPrev._renderNext = e; + _renderLast[e._layer] = e; + e._renderNext = null; + return true; + } + + /** + * Shifts the Entity one place towards the front of its contained layer. + * @param e The Entity to shift. + * @return If the Entity changed position. + */ + public function bringForward(e:Entity):Boolean + { + if (e._world !== this || !e._renderPrev) return false; + // pull from list + e._renderPrev._renderNext = e._renderNext; + if (e._renderNext) e._renderNext._renderPrev = e._renderPrev; + else _renderLast[e._layer] = e._renderPrev; + // shift towards the front + e._renderNext = e._renderPrev; + e._renderPrev = e._renderPrev._renderPrev; + e._renderNext._renderPrev = e; + if (e._renderPrev) e._renderPrev._renderNext = e; + else _renderFirst[e._layer] = e; + return true; + } + + /** + * Shifts the Entity one place towards the back of its contained layer. + * @param e The Entity to shift. + * @return If the Entity changed position. + */ + public function sendBackward(e:Entity):Boolean + { + if (e._world !== this || !e._renderNext) return false; + // pull from list + e._renderNext._renderPrev = e._renderPrev; + if (e._renderPrev) e._renderPrev._renderNext = e._renderNext; + else _renderFirst[e._layer] = e._renderNext; + // shift towards the back + e._renderPrev = e._renderNext; + e._renderNext = e._renderNext._renderNext; + e._renderPrev._renderNext = e; + if (e._renderNext) e._renderNext._renderPrev = e; + else _renderLast[e._layer] = e; + return true; + } + + /** + * If the Entity as at the front of its layer. + * @param e The Entity to check. + * @return True or false. + */ + public function isAtFront(e:Entity):Boolean + { + return e._renderPrev == null; + } + + /** + * If the Entity as at the back of its layer. + * @param e The Entity to check. + * @return True or false. + */ + public function isAtBack(e:Entity):Boolean + { + return e._renderNext == null; + } + + /** + * Returns the first Entity that collides with the rectangular area. + * @param type The Entity type to check for. + * @param rX X position of the rectangle. + * @param rY Y position of the rectangle. + * @param rWidth Width of the rectangle. + * @param rHeight Height of the rectangle. + * @return The first Entity to collide, or null if none collide. + */ + public function collideRect(type:String, rX:Number, rY:Number, rWidth:Number, rHeight:Number):Entity + { + var e:Entity = _typeFirst[type]; + while (e) + { + if (e.collideRect(e.x, e.y, rX, rY, rWidth, rHeight)) return e; + e = e._typeNext; + } + return null; + } + + /** + * Returns the first Entity found that collides with the position. + * @param type The Entity type to check for. + * @param pX X position. + * @param pY Y position. + * @return The collided Entity, or null if none collide. + */ + public function collidePoint(type:String, pX:Number, pY:Number):Entity + { + var e:Entity = _typeFirst[type]; + while (e) + { + if (e.collidePoint(e.x, e.y, pX, pY)) return e; + e = e._typeNext; + } + return null; + } + + /** + * Returns the first Entity found that collides with the line. + * @param type The Entity type to check for. + * @param fromX Start x of the line. + * @param fromY Start y of the line. + * @param toX End x of the line. + * @param toY End y of the line. + * @param precision + * @param p + * @return + */ + public function collideLine(type:String, fromX:int, fromY:int, toX:int, toY:int, precision:uint = 1, p:Point = null):Entity + { + // If the distance is less than precision, do the short sweep. + if (precision < 1) precision = 1; + if (FP.distance(fromX, fromY, toX, toY) < precision) + { + if (p) + { + if (fromX == toX && fromY == toY) + { + p.x = toX; p.y = toY; + return collidePoint(type, toX, toY); + } + return collideLine(type, fromX, fromY, toX, toY, 1, p); + } + else return collidePoint(type, fromX, toY); + } + + // Get information about the line we're about to raycast. + var xDelta:int = Math.abs(toX - fromX), + yDelta:int = Math.abs(toY - fromY), + xSign:Number = toX > fromX ? precision : -precision, + ySign:Number = toY > fromY ? precision : -precision, + x:Number = fromX, y:Number = fromY, e:Entity; + + // Do a raycast from the start to the end point. + if (xDelta > yDelta) + { + ySign *= yDelta / xDelta; + if (xSign > 0) + { + while (x < toX) + { + if ((e = collidePoint(type, x, y))) + { + if (!p) return e; + if (precision < 2) + { + p.x = x - xSign; p.y = y - ySign; + return e; + } + return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p); + } + x += xSign; y += ySign; + } + } + else + { + while (x > toX) + { + if ((e = collidePoint(type, x, y))) + { + if (!p) return e; + if (precision < 2) + { + p.x = x - xSign; p.y = y - ySign; + return e; + } + return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p); + } + x += xSign; y += ySign; + } + } + } + else + { + xSign *= xDelta / yDelta; + if (ySign > 0) + { + while (y < toY) + { + if ((e = collidePoint(type, x, y))) + { + if (!p) return e; + if (precision < 2) + { + p.x = x - xSign; p.y = y - ySign; + return e; + } + return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p); + } + x += xSign; y += ySign; + } + } + else + { + while (y > toY) + { + if ((e = collidePoint(type, x, y))) + { + if (!p) return e; + if (precision < 2) + { + p.x = x - xSign; p.y = y - ySign; + return e; + } + return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p); + } + x += xSign; y += ySign; + } + } + } + + // Check the last position. + if (precision > 1) + { + if (!p) return collidePoint(type, toX, toY); + if (collidePoint(type, toX, toY)) return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p); + } + + // No collision, return the end point. + if (p) + { + p.x = toX; + p.y = toY; + } + return null; + } + + /** + * Populates an array with all Entities that collide with the rectangle. This + * function does not empty the array, that responsibility is left to the user. + * @param type The Entity type to check for. + * @param rX X position of the rectangle. + * @param rY Y position of the rectangle. + * @param rWidth Width of the rectangle. + * @param rHeight Height of the rectangle. + * @param into The Array or Vector to populate with collided Entities. + */ + public function collideRectInto(type:String, rX:Number, rY:Number, rWidth:Number, rHeight:Number, into:Object):void + { + if (into is Array || into is Vector.<*>) + { + var e:Entity = _typeFirst[type], + n:uint = into.length; + while (e) + { + if (e.collideRect(e.x, e.y, rX, rY, rWidth, rHeight)) into[n ++] = e; + e = e._typeNext; + } + } + } + + /** + * Populates an array with all Entities that collide with the position. This + * function does not empty the array, that responsibility is left to the user. + * @param type The Entity type to check for. + * @param pX X position. + * @param pY Y position. + * @param into The Array or Vector to populate with collided Entities. + * @return The provided Array. + */ + public function collidePointInto(type:String, pX:Number, pY:Number, into:Object):void + { + if (into is Array || into is Vector.<*>) + { + var e:Entity = _typeFirst[type], + n:uint = into.length; + while (e) + { + if (e.collidePoint(e.x, e.y, pX, pY)) into[n ++] = e; + e = e._typeNext; + } + } + } + + /** + * Finds the Entity nearest to the rectangle. + * @param type The Entity type to check for. + * @param x X position of the rectangle. + * @param y Y position of the rectangle. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @return The nearest Entity to the rectangle. + */ + public function nearestToRect(type:String, x:Number, y:Number, width:Number, height:Number):Entity + { + var n:Entity = _typeFirst[type], + nearDist:Number = Number.MAX_VALUE, + near:Entity, dist:Number; + while (n) + { + dist = squareRects(x, y, width, height, n.x - n.originX, n.y - n.originY, n.width, n.height); + if (dist < nearDist) + { + nearDist = dist; + near = n; + } + n = n._typeNext; + } + return near; + } + + /** + * Finds the Entity nearest to another. + * @param type The Entity type to check for. + * @param e The Entity to find the nearest to. + * @param useHitboxes If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used. + * @return The nearest Entity to e. + */ + public function nearestToEntity(type:String, e:Entity, useHitboxes:Boolean = false):Entity + { + if (useHitboxes) return nearestToRect(type, e.x - e.originX, e.y - e.originY, e.width, e.height); + var n:Entity = _typeFirst[type], + nearDist:Number = Number.MAX_VALUE, + near:Entity, dist:Number, + x:Number = e.x - e.originX, + y:Number = e.y - e.originY; + while (n) + { + dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y); + if (dist < nearDist) + { + nearDist = dist; + near = n; + } + n = n._typeNext; + } + return near; + } + + /** + * Finds the Entity nearest to the position. + * @param type The Entity type to check for. + * @param x X position. + * @param y Y position. + * @param useHitboxes If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used. + * @return The nearest Entity to the position. + */ + public function nearestToPoint(type:String, x:Number, y:Number, useHitboxes:Boolean = false):Entity + { + var n:Entity = _typeFirst[type], + nearDist:Number = Number.MAX_VALUE, + near:Entity, dist:Number; + if (useHitboxes) + { + while (n) + { + dist = squarePointRect(x, y, n.x - n.originX, n.y - n.originY, n.width, n.height); + if (dist < nearDist) + { + nearDist = dist; + near = n; + } + n = n._typeNext; + } + return near; + } + while (n) + { + dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y); + if (dist < nearDist) + { + nearDist = dist; + near = n; + } + n = n._typeNext; + } + return near; + } + + /** + * How many Entities are in the World. + */ + public function get count():uint { return _count; } + + /** + * Returns the amount of Entities of the type are in the World. + * @param type The type (or Class type) to count. + * @return How many Entities of type exist in the World. + */ + public function typeCount(type:String):uint + { + return _typeCount[type] as uint; + } + + /** + * Returns the amount of Entities of the Class are in the World. + * @param c The Class type to count. + * @return How many Entities of Class exist in the World. + */ + public function classCount(c:Class):uint + { + return _classCount[c] as uint; + } + + /** + * Returns the amount of Entities are on the layer in the World. + * @param layer The layer to count Entities on. + * @return How many Entities are on the layer. + */ + public function layerCount(layer:int):uint + { + return _layerCount[layer] as uint; + } + + /** + * The first Entity in the World. + */ + public function get first():Entity { return _updateFirst; } + + /** + * How many Entity layers the World has. + */ + public function get layers():uint { return _layerList.length; } + + /** + * The first Entity of the type. + * @param type The type to check. + * @return The Entity. + */ + public function typeFirst(type:String):Entity + { + if (!_updateFirst) return null; + return _typeFirst[type] as Entity; + } + + /** + * The first Entity of the Class. + * @param c The Class type to check. + * @return The Entity. + */ + public function classFirst(c:Class):Entity + { + if (!_updateFirst) return null; + var e:Entity = _updateFirst; + while (e) + { + if (e is c) return e; + e = e._updateNext; + } + return null; + } + + /** + * The first Entity on the Layer. + * @param layer The layer to check. + * @return The Entity. + */ + public function layerFirst(layer:int):Entity + { + if (!_updateFirst) return null; + return _renderFirst[layer] as Entity; + } + + /** + * The last Entity on the Layer. + * @param layer The layer to check. + * @return The Entity. + */ + public function layerLast(layer:int):Entity + { + if (!_updateFirst) return null; + return _renderLast[layer] as Entity; + } + + /** + * The Entity that will be rendered first by the World. + */ + public function get farthest():Entity + { + if (!_updateFirst) return null; + return _renderLast[_layerList[_layerList.length - 1] as int] as Entity; + } + + /** + * The Entity that will be rendered last by the world. + */ + public function get nearest():Entity + { + if (!_updateFirst) return null; + return _renderFirst[_layerList[0] as int] as Entity; + } + + /** + * The layer that will be rendered first by the World. + */ + public function get layerFarthest():int + { + if (!_updateFirst) return 0; + return _layerList[_layerList.length - 1] as int; + } + + /** + * The layer that will be rendered last by the World. + */ + public function get layerNearest():int + { + if (!_updateFirst) return 0; + return _layerList[0] as int; + } + + /** + * How many different types have been added to the World. + */ + public function get uniqueTypes():uint + { + var i:uint = 0; + for (var type:String in _typeCount) i ++; + return i; + } + + /** + * Pushes all Entities in the World of the type into the Array or Vector. + * @param type The type to check. + * @param into The Array or Vector to populate. + * @return The same array, populated. + */ + public function getType(type:String, into:Object):void + { + if (into is Array || into is Vector.<*>) + { + var e:Entity = _typeFirst[type], + n:uint = into.length; + while (e) + { + into[n ++] = e; + e = e._typeNext; + } + } + } + + /** + * Pushes all Entities in the World of the Class into the Array or Vector. + * @param c The Class type to check. + * @param into The Array or Vector to populate. + * @return The same array, populated. + */ + public function getClass(c:Class, into:Object):void + { + if (into is Array || into is Vector.<*>) + { + var e:Entity = _updateFirst, + n:uint = into.length; + while (e) + { + if (e is c) into[n ++] = e; + e = e._updateNext; + } + } + } + + /** + * Pushes all Entities in the World on the layer into the Array or Vector. + * @param layer The layer to check. + * @param into The Array or Vector to populate. + * @return The same array, populated. + */ + public function getLayer(layer:int, into:Object):void + { + if (into is Array || into is Vector.<*>) + { + var e:Entity = _renderLast[layer], + n:uint = into.length; + while (e) + { + into[n ++] = e; + e = e._renderPrev; + } + } + } + + /** + * Pushes all Entities in the World into the array. + * @param into The Array or Vector to populate. + * @return The same array, populated. + */ + public function getAll(into:Object):void + { + if (into is Array || into is Vector.<*>) + { + var e:Entity = _updateFirst, + n:uint = into.length; + while (e) + { + into[n ++] = e; + e = e._updateNext; + } + } + } + + /** + * Updates the add/remove lists at the end of the frame. + */ + public function updateLists():void + { + var e:Entity; + + // remove entities + if (_remove.length) + { + for each (e in _remove) + { + if (e._added != true && _add.indexOf(e) >= 0) + { + _add.splice(_add.indexOf(e), 1); + continue; + } + e._added = false; + e.removed(); + removeUpdate(e); + removeRender(e); + if (e._type) removeType(e); + if (e.autoClear && e._tween) e.clearTweens(); + } + _remove.length = 0; + } + + // add entities + if (_add.length) + { + for each (e in _add) + { + e._added = true; + addUpdate(e); + addRender(e); + if (e._type) addType(e); + e.added(); + } + _add.length = 0; + } + + // sort the depth list + if (_layerSort) + { + if (_layerList.length > 1) FP.sort(_layerList, true); + _layerSort = false; + } + } + + /** @private Adds Entity to the update list. */ + private function addUpdate(e:Entity):void + { + // add to update list + if (_updateFirst) + { + _updateFirst._updatePrev = e; + e._updateNext = _updateFirst; + } + else e._updateNext = null; + e._updatePrev = null; + _updateFirst = e; + _count ++; + if (!_classCount[e._class]) _classCount[e._class] = 0; + _classCount[e._class] ++; + } + + /** @private Removes Entity from the update list. */ + private function removeUpdate(e:Entity):void + { + // remove from the update list + if (_updateFirst == e) _updateFirst = e._updateNext; + if (e._updateNext) e._updateNext._updatePrev = e._updatePrev; + if (e._updatePrev) e._updatePrev._updateNext = e._updateNext; + e._updateNext = e._updatePrev = null; + + _count --; + _classCount[e._class] --; + } + + /** @private Adds Entity to the render list. */ + internal function addRender(e:Entity):void + { + var f:Entity = _renderFirst[e._layer]; + if (f) + { + // Append entity to existing layer. + e._renderNext = f; + f._renderPrev = e; + _layerCount[e._layer] ++; + } + else + { + // Create new layer with entity. + _renderLast[e._layer] = e; + _layerList[_layerList.length] = e._layer; + _layerSort = true; + e._renderNext = null; + _layerCount[e._layer] = 1; + } + _renderFirst[e._layer] = e; + e._renderPrev = null; + } + + /** @private Removes Entity from the render list. */ + internal function removeRender(e:Entity):void + { + if (e._renderNext) e._renderNext._renderPrev = e._renderPrev; + else _renderLast[e._layer] = e._renderPrev; + if (e._renderPrev) e._renderPrev._renderNext = e._renderNext; + else + { + // Remove this entity from the layer. + _renderFirst[e._layer] = e._renderNext + if (!e._renderNext) + { + // Remove the layer from the layer list if this was the last entity. + if (_layerList.length > 1) + { + _layerList[_layerList.indexOf(e._layer)] = _layerList[_layerList.length - 1]; + _layerSort = true; + } + _layerList.length --; + } + } + _layerCount[e._layer] --; + e._renderNext = e._renderPrev = null; + } + + /** @private Adds Entity to the type list. */ + internal function addType(e:Entity):void + { + // add to type list + if (_typeFirst[e._type]) + { + _typeFirst[e._type]._typePrev = e; + e._typeNext = _typeFirst[e._type]; + _typeCount[e._type] ++; + } + else + { + e._typeNext = null; + _typeCount[e._type] = 1; + } + e._typePrev = null; + _typeFirst[e._type] = e; + } + + /** @private Removes Entity from the type list. */ + internal function removeType(e:Entity):void + { + // remove from the type list + if (_typeFirst[e._type] == e) _typeFirst[e._type] = e._typeNext; + if (e._typeNext) e._typeNext._typePrev = e._typePrev; + if (e._typePrev) e._typePrev._typeNext = e._typeNext; + e._typeNext = e._typePrev = null; + _typeCount[e._type] --; + } + + /** @private Calculates the squared distance between two rectangles. */ + private static function squareRects(x1:Number, y1:Number, w1:Number, h1:Number, x2:Number, y2:Number, w2:Number, h2:Number):Number + { + if (x1 < x2 + w2 && x2 < x1 + w1) + { + if (y1 < y2 + h2 && y2 < y1 + h1) return 0; + if (y1 > y2) return (y1 - (y2 + h2)) * (y1 - (y2 + h2)); + return (y2 - (y1 + h1)) * (y2 - (y1 + h1)); + } + if (y1 < y2 + h2 && y2 < y1 + h1) + { + if (x1 > x2) return (x1 - (x2 + w2)) * (x1 - (x2 + w2)); + return (x2 - (x1 + w1)) * (x2 - (x1 + w1)); + } + if (x1 > x2) + { + if (y1 > y2) return squarePoints(x1, y1, (x2 + w2), (y2 + h2)); + return squarePoints(x1, y1 + h1, x2 + w2, y2); + } + if (y1 > y2) return squarePoints(x1 + w1, y1, x2, y2 + h2) + return squarePoints(x1 + w1, y1 + h1, x2, y2); + } + + /** @private Calculates the squared distance between two points. */ + private static function squarePoints(x1:Number, y1:Number, x2:Number, y2:Number):Number + { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + } + + /** @private Calculates the squared distance between a rectangle and a point. */ + private static function squarePointRect(px:Number, py:Number, rx:Number, ry:Number, rw:Number, rh:Number):Number + { + if (px >= rx && px <= rx + rw) + { + if (py >= ry && py <= ry + rh) return 0; + if (py > ry) return (py - (ry + rh)) * (py - (ry + rh)); + return (ry - py) * (ry - py); + } + if (py >= ry && py <= ry + rh) + { + if (px > rx) return (px - (rx + rw)) * (px - (rx + rw)); + return (rx - px) * (rx - px); + } + if (px > rx) + { + if (py > ry) return squarePoints(px, py, rx + rw, ry + rh); + return squarePoints(px, py, rx + rw, ry); + } + if (py > ry) return squarePoints(px, py, rx, ry + rh) + return squarePoints(px, py, rx, ry); + } + + // Adding and removal. + /** @private */ private var _add:Vector. = new Vector.; + /** @private */ private var _remove:Vector. = new Vector.; + + // Update information. + /** @private */ private var _updateFirst:Entity; + /** @private */ private var _count:uint; + + // Render information. + private var _renderFirst:Array = []; + private var _renderLast:Array = []; + private var _layerList:Array = []; + private var _layerCount:Array = []; + private var _layerSort:Boolean; + private var _tempArray:Array = []; + + /** @private */ private var _classCount:Dictionary = new Dictionary; + /** @private */ internal var _typeFirst:Object = { }; + /** @private */ private var _typeCount:Object = { }; + /** @private */ private var _recycled:Dictionary = new Dictionary; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/debug/Console.as b/src/net/flashpunk/debug/Console.as new file mode 100644 index 0000000..3b973f1 --- /dev/null +++ b/src/net/flashpunk/debug/Console.as @@ -0,0 +1,878 @@ +package net.flashpunk.debug +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.display.Stage; + import flash.geom.ColorTransform; + import flash.geom.Rectangle; + import flash.text.TextField; + import flash.text.TextFormat; + import net.flashpunk.Entity; + import net.flashpunk.FP; + import net.flashpunk.utils.Draw; + import net.flashpunk.utils.Input; + import net.flashpunk.utils.Key; + + /** + * FlashPunk debug console; can use to log information or pause the game and view/move Entities and step the frame. + */ + public class Console + { + /** + * The key used to toggle the Console on/off. Tilde (~) by default. + */ + public var toggleKey:uint = 192; + + /** + * Constructor. + */ + public function Console() + { + Input.define("_ARROWS", Key.RIGHT, Key.LEFT, Key.DOWN, Key.UP); + } + + /** + * Logs data to the console. + * @param ...data The data parameters to log, can be variables, objects, etc. Parameters will be separated by a space (" "). + */ + public function log(...data):void + { + var s:String; + if (data.length > 1) + { + s = ""; + var i:int = 0; + while (i < data.length) + { + if (i > 0) s += " "; + s += data[i ++].toString(); + } + } + else s = data[0].toString(); + if (s.indexOf("\n") >= 0) + { + var a:Array = s.split("\n"); + for each (s in a) LOG.push(s); + } + else LOG.push(s); + if (_enabled && _sprite.visible) updateLog(); + } + + /** + * Adds properties to watch in the console's debug panel. + * @param ...properties The properties (strings) to watch. + */ + public function watch(...properties):void + { + var i:String; + if (properties.length > 1) + { + for each (i in properties) WATCH_LIST.push(i); + } + else if (properties[0] is Array || properties[0] is Vector.<*>) + { + for each (i in properties[0]) WATCH_LIST.push(i); + } + else WATCH_LIST.push(properties[0]); + } + + /** + * Enables the console. + */ + public function enable():void + { + // Quit if the console is already enabled. + if (_enabled) return; + + // Enable it and add the Sprite to the stage. + _enabled = true; + FP.engine.addChild(_sprite); + + // Used to determine some text sizing. + var big:Boolean = width >= 480; + + // The transparent FlashPunk logo overlay bitmap. + _sprite.addChild(_back); + _back.bitmapData = new BitmapData(width, height, true, 0xFFFFFFFF); + var b:BitmapData = (new CONSOLE_LOGO).bitmapData; + FP.matrix.identity(); + FP.matrix.tx = Math.max((_back.bitmapData.width - b.width) / 2, 0); + FP.matrix.ty = Math.max((_back.bitmapData.height - b.height) / 2, 0); + FP.matrix.scale(Math.min(width / _back.bitmapData.width, 1), Math.min(height / _back.bitmapData.height, 1)); + _back.bitmapData.draw(b, FP.matrix, null, BlendMode.MULTIPLY); + _back.bitmapData.draw(_back.bitmapData, null, null, BlendMode.INVERT); + _back.bitmapData.colorTransform(_back.bitmapData.rect, new ColorTransform(1, 1, 1, 0.5)); + + // The entity and selection sprites. + _sprite.addChild(_entScreen); + _entScreen.addChild(_entSelect); + + // The entity count text. + _sprite.addChild(_entRead); + _entRead.addChild(_entReadText); + _entReadText.defaultTextFormat = format(16, 0xFFFFFF, "right"); + _entReadText.embedFonts = true; + _entReadText.width = 100; + _entReadText.height = 20; + _entRead.x = width - _entReadText.width; + + // The entity count panel. + _entRead.graphics.clear(); + _entRead.graphics.beginFill(0, .5); + _entRead.graphics.drawRoundRectComplex(0, 0, _entReadText.width, 20, 0, 0, 20, 0); + + // The FPS text. + _sprite.addChild(_fpsRead); + _fpsRead.addChild(_fpsReadText); + _fpsReadText.defaultTextFormat = format(16); + _fpsReadText.embedFonts = true; + _fpsReadText.width = 70; + _fpsReadText.height = 20; + _fpsReadText.x = 2; + _fpsReadText.y = 1; + + // The FPS and frame timing panel. + _fpsRead.graphics.clear(); + _fpsRead.graphics.beginFill(0, .75); + _fpsRead.graphics.drawRoundRectComplex(0, 0, big ? 200 : 100, 20, 0, 0, 0, 20); + + // The frame timing text. + if (big) _sprite.addChild(_fpsInfo); + _fpsInfo.addChild(_fpsInfoText0); + _fpsInfo.addChild(_fpsInfoText1); + _fpsInfoText0.defaultTextFormat = format(8, 0xAAAAAA); + _fpsInfoText1.defaultTextFormat = format(8, 0xAAAAAA); + _fpsInfoText0.embedFonts = true; + _fpsInfoText1.embedFonts = true; + _fpsInfoText0.width = _fpsInfoText1.width = 60; + _fpsInfoText0.height = _fpsInfoText1.height = 20; + _fpsInfo.x = 75; + _fpsInfoText1.x = 60; + + // The output log text. + _sprite.addChild(_logRead); + _logRead.addChild(_logReadText0); + _logRead.addChild(_logReadText1); + _logReadText0.defaultTextFormat = format(16, 0xFFFFFF); + _logReadText1.defaultTextFormat = format(big ? 16 : 8, 0xFFFFFF); + _logReadText0.embedFonts = true; + _logReadText1.embedFonts = true; + _logReadText0.selectable = false; + _logReadText0.width = 80; + _logReadText0.height = 20; + _logReadText1.width = width; + _logReadText0.x = 2; + _logReadText0.y = 3; + _logReadText0.text = "OUTPUT:"; + _logHeight = height - 60; + _logBar = new Rectangle(8, 24, 16, _logHeight - 8); + _logBarGlobal = _logBar.clone(); + _logBarGlobal.y += 40; + _logLines = _logHeight / (big ? 16.5 : 8.5); + + // The debug text. + _sprite.addChild(_debRead); + _debRead.addChild(_debReadText0); + _debRead.addChild(_debReadText1); + _debReadText0.defaultTextFormat = format(16, 0xFFFFFF); + _debReadText1.defaultTextFormat = format(8, 0xFFFFFF); + _debReadText0.embedFonts = true; + _debReadText1.embedFonts = true; + _debReadText0.selectable = false; + _debReadText0.width = 80; + _debReadText0.height = 20; + _debReadText1.width = 160; + _debReadText1.height = int(height / 4); + _debReadText0.x = 2; + _debReadText0.y = 3; + _debReadText1.x = 2; + _debReadText1.y = 24; + _debReadText0.text = "DEBUG:"; + _debRead.y = height - (_debReadText1.y + _debReadText1.height); + + // The button panel buttons. + _sprite.addChild(_butRead); + _butRead.addChild(_butDebug = new CONSOLE_DEBUG); + _butRead.addChild(_butOutput = new CONSOLE_OUTPUT); + _butRead.addChild(_butPlay = new CONSOLE_PLAY).x = 20; + _butRead.addChild(_butPause = new CONSOLE_PAUSE).x = 20; + _butRead.addChild(_butStep = new CONSOLE_STEP).x = 40; + updateButtons(); + + // The button panel. + _butRead.graphics.clear(); + _butRead.graphics.beginFill(0, .75); + _butRead.graphics.drawRoundRectComplex(-20, 0, 100, 20, 0, 0, 20, 20); + + // Set the state to unpaused. + paused = false; + } + + /** + * If the console should be visible. + */ + public function get visible():Boolean { return _sprite.visible; } + public function set visible(value:Boolean):void + { + _sprite.visible = value; + if (_enabled && value) updateLog(); + } + + /** + * Console update, called by game loop. + */ + public function update():void + { + // Quit if the console isn't enabled. + if (!_enabled) return; + + // If the console is paused. + if (_paused) + { + // Update buttons. + updateButtons(); + + // While in debug mode. + if (_debug) + { + // While the game is paused. + if (FP.engine.paused) + { + // When the mouse is pressed. + if (Input.mousePressed) + { + // Mouse is within clickable area. + if (Input.mouseFlashY > 20 && (Input.mouseFlashX > _debReadText1.width || Input.mouseFlashY < _debRead.y)) + { + if (Input.check(Key.SHIFT)) + { + if (SELECT_LIST.length) startDragging(); + else startPanning(); + } + else startSelection(); + } + } + else + { + // Update mouse movement functions. + if (_selecting) updateSelection(); + if (_dragging) updateDragging(); + if (_panning) updatePanning(); + } + + // Select all Entities + if (Input.pressed(Key.A)) selectAll(); + + // If the shift key is held. + if (Input.check(Key.SHIFT)) + { + // If Entities are selected. + if (SELECT_LIST.length) + { + // Move Entities with the arrow keys. + if (Input.pressed("_ARROWS")) updateKeyMoving(); + } + else + { + // Pan the camera with the arrow keys. + if (Input.check("_ARROWS")) updateKeyPanning(); + } + } + } + else + { + // Update info while the game runs. + updateEntityLists(FP.world.count != ENTITY_LIST.length); + renderEntities(); + updateFPSRead(); + updateEntityCount(); + } + + // Update debug panel. + updateDebugRead(); + } + else + { + // log scrollbar + if (_scrolling) updateScrolling(); + else if (Input.mousePressed) startScrolling(); + } + } + else + { + // Update info while the game runs. + updateFPSRead(); + updateEntityCount(); + } + + // Console toggle. + if (Input.pressed(toggleKey)) paused = !_paused; + } + + /** + * If the Console is currently in paused mode. + */ + public function get paused():Boolean { return _paused; } + public function set paused(value:Boolean):void + { + // Quit if the console isn't enabled. + if (!_enabled) return; + + // Set the console to paused. + _paused = value; + FP.engine.paused = value; + + // Panel visibility. + _back.visible = value; + _entScreen.visible = value; + _butRead.visible = value; + + // If the console is paused. + if (value) + { + // Set the console to paused mode. + if (_debug) debug = true; + else updateLog(); + } + else + { + // Set the console to running mode. + _debRead.visible = false; + _logRead.visible = true; + updateLog(); + ENTITY_LIST.length = 0; + SCREEN_LIST.length = 0; + SELECT_LIST.length = 0; + } + } + + /** + * If the Console is currently in debug mode. + */ + public function get debug():Boolean { return _debug; } + public function set debug(value:Boolean):void + { + // Quit if the console isn't enabled. + if (!_enabled) return; + + // Set the console to debug mode. + _debug = value; + _debRead.visible = value; + _logRead.visible = !value; + + // Update console state. + if (value) updateEntityLists(); + else updateLog(); + renderEntities(); + } + + /** @private Steps the frame ahead. */ + private function stepFrame():void + { + FP.engine.update(); + FP.engine.render(); + updateEntityCount(); + updateEntityLists(); + renderEntities(); + } + + /** @private Starts Entity dragging. */ + private function startDragging():void + { + _dragging = true; + _entRect.x = Input.mouseX; + _entRect.y = Input.mouseY; + } + + /** @private Updates Entity dragging. */ + private function updateDragging():void + { + moveSelected(Input.mouseX - _entRect.x, Input.mouseY - _entRect.y); + _entRect.x = Input.mouseX; + _entRect.y = Input.mouseY; + if (Input.mouseReleased) _dragging = false; + } + + /** @private Move the selected Entitites by the amount. */ + private function moveSelected(xDelta:int, yDelta:int):void + { + for each (var e:Entity in SELECT_LIST) + { + e.x += xDelta; + e.y += yDelta; + } + FP.engine.render(); + renderEntities(); + updateEntityLists(true); + } + + /** @private Starts camera panning. */ + private function startPanning():void + { + _panning = true; + _entRect.x = Input.mouseX; + _entRect.y = Input.mouseY; + } + + /** @private Updates camera panning. */ + private function updatePanning():void + { + if (Input.mouseReleased) _panning = false; + panCamera(_entRect.x - Input.mouseX, _entRect.y - Input.mouseY); + _entRect.x = Input.mouseX; + _entRect.y = Input.mouseY; + } + + /** @private Pans the camera. */ + private function panCamera(xDelta:int, yDelta:int):void + { + FP.camera.x += xDelta; + FP.camera.y += yDelta; + FP.engine.render(); + updateEntityLists(true); + renderEntities(); + } + + /** @private Sets the camera position. */ + private function setCamera(x:int, y:int):void + { + FP.camera.x = x; + FP.camera.y = y; + FP.engine.render(); + updateEntityLists(true); + renderEntities(); + } + + /** @private Starts Entity selection. */ + private function startSelection():void + { + _selecting = true; + _entRect.x = Input.mouseFlashX; + _entRect.y = Input.mouseFlashY; + _entRect.width = 0; + _entRect.height = 0; + } + + /** @private Updates Entity selection. */ + private function updateSelection():void + { + _entRect.width = Input.mouseFlashX - _entRect.x; + _entRect.height = Input.mouseFlashY - _entRect.y; + if (Input.mouseReleased) + { + selectEntities(_entRect); + renderEntities(); + _selecting = false; + _entSelect.graphics.clear(); + } + else + { + _entSelect.graphics.clear(); + _entSelect.graphics.lineStyle(1, 0xFFFFFF); + _entSelect.graphics.drawRect(_entRect.x, _entRect.y, _entRect.width, _entRect.height); + } + } + + /** @private Selects the Entitites in the rectangle. */ + private function selectEntities(rect:Rectangle):void + { + if (rect.width < 0) rect.x -= (rect.width = -rect.width); + else if (!rect.width) rect.width = 1; + if (rect.height < 0) rect.y -= (rect.height = -rect.height); + else if (!rect.height) rect.height = 1; + + FP.rect.width = FP.rect.height = 6; + var sx:Number = FP.screen.scaleX * FP.screen.scale, + sy:Number = FP.screen.scaleY * FP.screen.scale, + e:Entity; + + if (Input.check(Key.CONTROL)) + { + // Append selected Entitites with new selections. + for each (e in SCREEN_LIST) + { + if (SELECT_LIST.indexOf(e) < 0) + { + FP.rect.x = (e.x - FP.camera.x) * sx - 3; + FP.rect.y = (e.y - FP.camera.y) * sy - 3; + if (rect.intersects(FP.rect)) SELECT_LIST.push(e); + } + } + } + else + { + // Replace selections with new selections. + SELECT_LIST.length = 0; + for each (e in SCREEN_LIST) + { + FP.rect.x = (e.x - FP.camera.x) * sx - 3; + FP.rect.y = (e.y - FP.camera.y) * sy - 3; + if (rect.intersects(FP.rect)) SELECT_LIST.push(e); + } + } + } + + /** @private Selects all entities on screen. */ + private function selectAll():void + { + SELECT_LIST.length = 0; + for each (var e:Entity in SCREEN_LIST) SELECT_LIST.push(e); + renderEntities(); + } + + /** @private Starts log text scrolling. */ + private function startScrolling():void + { + if (LOG.length > _logLines) _scrolling = _logBarGlobal.contains(Input.mouseFlashX, Input.mouseFlashY); + } + + /** @private Updates log text scrolling. */ + private function updateScrolling():void + { + _scrolling = Input.mouseDown; + _logScroll = FP.scaleClamp(Input.mouseFlashY, _logBarGlobal.y, _logBarGlobal.bottom, 0, 1); + updateLog(); + } + + /** @private Moves Entities with the arrow keys. */ + private function updateKeyMoving():void + { + FP.point.x = (Input.pressed(Key.RIGHT) ? 1 : 0) - (Input.pressed(Key.LEFT) ? 1 : 0); + FP.point.y = (Input.pressed(Key.DOWN) ? 1 : 0) - (Input.pressed(Key.UP) ? 1 : 0); + if (FP.point.x != 0 || FP.point.y != 0) moveSelected(FP.point.x, FP.point.y); + } + + /** @private Pans the camera with the arrow keys. */ + private function updateKeyPanning():void + { + FP.point.x = (Input.check(Key.RIGHT) ? 1 : 0) - (Input.check(Key.LEFT) ? 1 : 0); + FP.point.y = (Input.check(Key.DOWN) ? 1 : 0) - (Input.check(Key.UP) ? 1 : 0); + if (FP.point.x != 0 || FP.point.y != 0) panCamera(FP.point.x, FP.point.y); + } + + /** @private Update the Entity list information. */ + private function updateEntityLists(fetchList:Boolean = true):void + { + // If the list should be re-populated. + if (fetchList) + { + ENTITY_LIST.length = 0; + FP.world.getAll(ENTITY_LIST); + } + + // Update the list of Entities on screen. + SCREEN_LIST.length = 0; + for each (var e:Entity in ENTITY_LIST) + { + if (e.collideRect(e.x, e.y, FP.camera.x, FP.camera.y, FP.width, FP.height)) + SCREEN_LIST.push(e); + } + } + + /** @private Renders the Entities positions and hitboxes. */ + private function renderEntities():void + { + // If debug mode is on. + _entScreen.visible = _debug; + if (_debug) + { + var g:Graphics = _entScreen.graphics, + sx:Number = FP.screen.scaleX * FP.screen.scale, + sy:Number = FP.screen.scaleY * FP.screen.scale; + g.clear(); + for each (var e:Entity in SCREEN_LIST) + { + // If the Entity is not selected. + if (SELECT_LIST.indexOf(e) < 0) + { + // Draw the normal hitbox and position. + if (e.width && e.height) + { + g.lineStyle(1, 0xFF0000); + g.drawRect((e.x - e.originX - FP.camera.x) * sx, (e.y - e.originY - FP.camera.y) * sy, e.width * sx, e.height * sy); + } + g.lineStyle(1, 0x00FF00); + g.drawRect((e.x - FP.camera.x) * sx - 3, (e.y - FP.camera.y) * sy - 3, 6, 6); + } + else + { + // Draw the selected hitbox and position. + if (e.width && e.height) + { + g.lineStyle(1, 0xFFFFFF); + g.drawRect((e.x - e.originX - FP.camera.x) * sx, (e.y - e.originY - FP.camera.y) * sy, e.width * sx, e.height * sy); + } + g.lineStyle(1, 0xFFFFFF); + g.drawRect((e.x - FP.camera.x) * sx - 3, (e.y - FP.camera.y) * sy - 3, 6, 6); + } + } + } + } + + /** @private Updates the log window. */ + private function updateLog():void + { + // If the console is paused. + if (_paused) + { + // Draw the log panel. + _logRead.y = 40; + _logRead.graphics.clear(); + _logRead.graphics.beginFill(0, .75); + _logRead.graphics.drawRoundRectComplex(0, 0, _logReadText0.width, 20, 0, 20, 0, 0); + _logRead.graphics.drawRect(0, 20, width, _logHeight); + + // Draw the log scrollbar. + _logRead.graphics.beginFill(0x202020, 1); + _logRead.graphics.drawRoundRectComplex(_logBar.x, _logBar.y, _logBar.width, _logBar.height, 8, 8, 8, 8); + + // If the log has more lines than the display limit. + if (LOG.length > _logLines) + { + // Draw the log scrollbar handle. + _logRead.graphics.beginFill(0xFFFFFF, 1); + var h:uint = FP.clamp(_logBar.height * (_logLines / LOG.length), 12, _logBar.height - 4), + y:uint = _logBar.y + 2 + (_logBar.height - 16) * _logScroll; + _logRead.graphics.drawRoundRectComplex(_logBar.x + 2, y, 12, 12, 6, 6, 6, 6); + } + + // Display the log text lines. + if (LOG.length) + { + var i:int = LOG.length > _logLines ? Math.round((LOG.length - _logLines) * _logScroll) : 0, + n:int = i + Math.min(_logLines, LOG.length), + s:String = ""; + while (i < n) s += LOG[i ++] + "\n"; + _logReadText1.text = s; + } + else _logReadText1.text = ""; + + // Indent the text for the scrollbar and size it to the log panel. + _logReadText1.height = _logHeight; + _logReadText1.x = 32; + _logReadText1.y = 24; + + // Make text selectable in paused mode. + _fpsReadText.selectable = true; + _fpsInfoText0.selectable = true; + _fpsInfoText1.selectable = true; + _entReadText.selectable = true; + _debReadText1.selectable = true; + } + else + { + // Draw the single-line log panel. + _logRead.y = height - 40; + _logReadText1.height = 20; + _logRead.graphics.clear(); + _logRead.graphics.beginFill(0, .75); + _logRead.graphics.drawRoundRectComplex(0, 0, _logReadText0.width, 20, 0, 20, 0, 0); + _logRead.graphics.drawRect(0, 20, width, 20); + + // Draw the single-line log text with the latests logged text. + _logReadText1.text = LOG.length ? LOG[LOG.length - 1] : ""; + _logReadText1.x = 2; + _logReadText1.y = 21; + + // Make text non-selectable while running. + _logReadText1.selectable = false; + _fpsReadText.selectable = false; + _fpsInfoText0.selectable = false; + _fpsInfoText1.selectable = false; + _entReadText.selectable = false; + _debReadText0.selectable = false; + _debReadText1.selectable = false; + } + } + + /** @private Update the FPS/frame timing panel text. */ + private function updateFPSRead():void + { + _fpsReadText.text = "FPS: " + FP.frameRate.toFixed(); + _fpsInfoText0.text = + "Update: " + String(FP._updateTime) + "ms\n" + + "Render: " + String(FP._renderTime) + "ms"; + _fpsInfoText1.text = + "Game: " + String(FP._gameTime) + "ms\n" + + "Flash: " + String(FP._flashTime) + "ms"; + } + + /** @private Update the debug panel text. */ + private function updateDebugRead():void + { + // Find out the screen size and set the text. + var big:Boolean = width >= 480; + + // Update the Debug read text. + var s:String = + "Mouse: " + String(FP.world.mouseX) + ", " + String(FP.world.mouseY) + + "\nCamera: " + String(FP.camera.x) + ", " + String(FP.camera.y); + if (SELECT_LIST.length) + { + if (SELECT_LIST.length > 1) + { + s += "\n\nSelected: " + String(SELECT_LIST.length); + } + else + { + var e:Entity = SELECT_LIST[0]; + s += "\n\n- " + String(e) + " -\n"; + for each (var i:String in WATCH_LIST) + { + if (e.hasOwnProperty(i)) s += "\n" + i + ": " + e[i].toString(); + } + } + } + + // Set the text and format. + _debReadText1.text = s; + _debReadText1.setTextFormat(format(big ? 16 : 8)); + _debReadText1.width = Math.max(_debReadText1.textWidth + 4, _debReadText0.width); + _debReadText1.height = _debReadText1.y + _debReadText1.textHeight + 4; + + // The debug panel. + _debRead.y = int(height - _debReadText1.height); + _debRead.graphics.clear(); + _debRead.graphics.beginFill(0, .75); + _debRead.graphics.drawRoundRectComplex(0, 0, _debReadText0.width, 20, 0, 20, 0, 0); + _debRead.graphics.drawRoundRectComplex(0, 20, _debReadText1.width + 20, height - _debRead.y - 20, 0, 20, 0, 0); + } + + /** @private Updates the Entity count text. */ + private function updateEntityCount():void + { + _entReadText.text = String(FP.world.count) + " Entities"; + } + + /** @private Updates the Button panel. */ + private function updateButtons():void + { + // Button visibility. + _butRead.x = _fpsInfo.x + _fpsInfo.width + int((_entRead.x - (_fpsInfo.x + _fpsInfo.width)) / 2) - 30; + _butDebug.visible = !_debug; + _butOutput.visible = _debug; + _butPlay.visible = FP.engine.paused; + _butPause.visible = !FP.engine.paused; + + // Debug/Output button. + if (_butDebug.bitmapData.rect.contains(_butDebug.mouseX, _butDebug.mouseY)) + { + _butDebug.alpha = _butOutput.alpha = 1; + if (Input.mousePressed) debug = !_debug; + } + else _butDebug.alpha = _butOutput.alpha = .5; + + // Play/Pause button. + if (_butPlay.bitmapData.rect.contains(_butPlay.mouseX, _butPlay.mouseY)) + { + _butPlay.alpha = _butPause.alpha = 1; + if (Input.mousePressed) + { + FP.engine.paused = !FP.engine.paused; + renderEntities(); + } + } + else _butPlay.alpha = _butPause.alpha = .5; + + // Frame step button. + if (_butStep.bitmapData.rect.contains(_butStep.mouseX, _butStep.mouseY)) + { + _butStep.alpha = 1; + if (Input.mousePressed) stepFrame(); + } + else _butStep.alpha = .5; + } + + /** @private Gets a TextFormat object with the formatting. */ + private function format(size:uint = 16, color:uint = 0xFFFFFF, align:String = "left"):TextFormat + { + _format.size = size; + _format.color = color; + _format.align = align; + return _format; + } + + /** + * Get the unscaled screen size for the Console. + */ + private function get width():uint { return FP.width * FP.screen.scaleX * FP.screen.scale; } + private function get height():uint { return FP.height * FP.screen.scaleY * FP.screen.scale; } + + // Console state information. + /** @private */ private var _enabled:Boolean; + /** @private */ private var _paused:Boolean; + /** @private */ private var _debug:Boolean; + /** @private */ private var _scrolling:Boolean; + /** @private */ private var _selecting:Boolean; + /** @private */ private var _dragging:Boolean; + /** @private */ private var _panning:Boolean; + + // Console display objects. + /** @private */ private var _sprite:Sprite = new Sprite; + /** @private */ private var _format:TextFormat = new TextFormat("console"); + /** @private */ private var _back:Bitmap = new Bitmap; + + // FPS panel information. + /** @private */ private var _fpsRead:Sprite = new Sprite; + /** @private */ private var _fpsReadText:TextField = new TextField; + /** @private */ private var _fpsInfo:Sprite = new Sprite; + /** @private */ private var _fpsInfoText0:TextField = new TextField; + /** @private */ private var _fpsInfoText1:TextField = new TextField; + + // Output panel information. + /** @private */ private var _logRead:Sprite = new Sprite; + /** @private */ private var _logReadText0:TextField = new TextField; + /** @private */ private var _logReadText1:TextField = new TextField; + /** @private */ private var _logHeight:uint; + /** @private */ private var _logBar:Rectangle; + /** @private */ private var _logBarGlobal:Rectangle; + /** @private */ private var _logScroll:Number = 0; + + // Entity count panel information. + /** @private */ private var _entRead:Sprite = new Sprite; + /** @private */ private var _entReadText:TextField = new TextField; + + // Debug panel information. + /** @private */ private var _debRead:Sprite = new Sprite; + /** @private */ private var _debReadText0:TextField = new TextField; + /** @private */ private var _debReadText1:TextField = new TextField; + /** @private */ private var _debWidth:uint; + + // Button panel information + /** @private */ private var _butRead:Sprite = new Sprite; + /** @private */ private var _butDebug:Bitmap; + /** @private */ private var _butOutput:Bitmap; + /** @private */ private var _butPlay:Bitmap; + /** @private */ private var _butPause:Bitmap; + /** @private */ private var _butStep:Bitmap; + + // Entity selection information. + /** @private */ private var _entScreen:Sprite = new Sprite; + /** @private */ private var _entSelect:Sprite = new Sprite; + /** @private */ private var _entRect:Rectangle = new Rectangle; + + // Log information. + /** @private */ private var _logLines:uint = 33; + /** @private */ private const LOG:Vector. = new Vector.; + + // Entity lists. + /** @private */ private const ENTITY_LIST:Vector. = new Vector.; + /** @private */ private const SCREEN_LIST:Vector. = new Vector.; + /** @private */ private const SELECT_LIST:Vector. = new Vector.; + + // Watch information. + /** @private */ private const WATCH_LIST:Vector. = Vector.(["x", "y"]); + + // Embedded assets. + [Embed(source = '../graphics/04B_03__.TTF', embedAsCFF="false", fontFamily = 'console')] private const FONT_SMALL:Class; + [Embed(source = 'console_logo.png')] private const CONSOLE_LOGO:Class; + [Embed(source = 'console_debug.png')] private const CONSOLE_DEBUG:Class; + [Embed(source = 'console_output.png')] private const CONSOLE_OUTPUT:Class; + [Embed(source = 'console_play.png')] private const CONSOLE_PLAY:Class; + [Embed(source = 'console_pause.png')] private const CONSOLE_PAUSE:Class; + [Embed(source = 'console_step.png')] private const CONSOLE_STEP:Class; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/debug/console_debug.png b/src/net/flashpunk/debug/console_debug.png new file mode 100644 index 0000000..9e49800 Binary files /dev/null and b/src/net/flashpunk/debug/console_debug.png differ diff --git a/src/net/flashpunk/debug/console_logo.png b/src/net/flashpunk/debug/console_logo.png new file mode 100644 index 0000000..430f5c3 Binary files /dev/null and b/src/net/flashpunk/debug/console_logo.png differ diff --git a/src/net/flashpunk/debug/console_output.png b/src/net/flashpunk/debug/console_output.png new file mode 100644 index 0000000..7d011ac Binary files /dev/null and b/src/net/flashpunk/debug/console_output.png differ diff --git a/src/net/flashpunk/debug/console_pause.png b/src/net/flashpunk/debug/console_pause.png new file mode 100644 index 0000000..3e7f58b Binary files /dev/null and b/src/net/flashpunk/debug/console_pause.png differ diff --git a/src/net/flashpunk/debug/console_play.png b/src/net/flashpunk/debug/console_play.png new file mode 100644 index 0000000..a34d201 Binary files /dev/null and b/src/net/flashpunk/debug/console_play.png differ diff --git a/src/net/flashpunk/debug/console_step.png b/src/net/flashpunk/debug/console_step.png new file mode 100644 index 0000000..4292f85 Binary files /dev/null and b/src/net/flashpunk/debug/console_step.png differ diff --git a/src/net/flashpunk/graphics/04B_03__.TTF b/src/net/flashpunk/graphics/04B_03__.TTF new file mode 100644 index 0000000..fe4328b Binary files /dev/null and b/src/net/flashpunk/graphics/04B_03__.TTF differ diff --git a/src/net/flashpunk/graphics/Anim.as b/src/net/flashpunk/graphics/Anim.as new file mode 100644 index 0000000..5d38116 --- /dev/null +++ b/src/net/flashpunk/graphics/Anim.as @@ -0,0 +1,66 @@ +package net.flashpunk.graphics +{ + /** + * Template used by Spritemap to define animations. Don't create + * these yourself, instead you can fetch them with Spritemap's add(). + */ + public class Anim + { + /** + * Constructor. + * @param name Animation name. + * @param frames Array of frame indices to animate. + * @param frameRate Animation speed. + * @param loop If the animation should loop. + */ + public function Anim(name:String, frames:Array, frameRate:Number = 0, loop:Boolean = true) + { + _name = name; + _frames = frames; + _frameRate = frameRate; + _loop = loop; + _frameCount = frames.length; + } + + /** + * Plays the animation. + * @param reset If the animation should force-restart if it is already playing. + */ + public function play(reset:Boolean = false):void + { + _parent.play(_name, reset); + } + + /** + * Name of the animation. + */ + public function get name():String { return _name; } + + /** + * Array of frame indices to animate. + */ + public function get frames():Array { return _frames; } + + /** + * Animation speed. + */ + public function get frameRate():Number { return _frameRate; } + + /** + * Amount of frames in the animation. + */ + public function get frameCount():uint { return _frameCount; } + + /** + * If the animation loops. + */ + public function get loop():Boolean { return _loop; } + + /** @private */ internal var _parent:Spritemap; + /** @private */ internal var _name:String; + /** @private */ internal var _frames:Array; + /** @private */ internal var _frameRate:Number; + /** @private */ internal var _frameCount:uint; + /** @private */ internal var _loop:Boolean; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Backdrop.as b/src/net/flashpunk/graphics/Backdrop.as new file mode 100644 index 0000000..18cef72 --- /dev/null +++ b/src/net/flashpunk/graphics/Backdrop.as @@ -0,0 +1,69 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.Point; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + + /** + * A background texture that can be repeated horizontally and vertically + * when drawn. Really useful for parallax backgrounds, textures, etc. + */ + public class Backdrop extends Canvas + { + /** + * Constructor. + * @param texture Source texture. + * @param repeatX Repeat horizontally. + * @param repeatY Repeat vertically. + */ + public function Backdrop(texture:*, repeatX:Boolean = true, repeatY:Boolean = true) + { + if (texture is Class) _texture = FP.getBitmap(texture); + else if (texture is BitmapData) _texture = texture; + if (!_texture) _texture = new BitmapData(FP.width, FP.height, true, 0); + _repeatX = repeatX; + _repeatY = repeatY; + _textWidth = _texture.width; + _textHeight = _texture.height; + super(FP.width * uint(repeatX) + _textWidth, FP.height * uint(repeatY) + _textHeight); + FP.rect.x = FP.rect.y = 0; + FP.rect.width = _width; + FP.rect.height = _height; + fillTexture(FP.rect, _texture); + } + + /** @private Renders the Backdrop. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + _point.x = point.x + x - camera.x * scrollX; + _point.y = point.y + y - camera.y * scrollY; + + if (_repeatX) + { + _point.x %= _textWidth; + if (_point.x > 0) _point.x -= _textWidth; + } + + if (_repeatY) + { + _point.y %= _textHeight; + if (_point.y > 0) _point.y -= _textHeight; + } + + _x = x; _y = y; + x = y = 0; + super.render(target, _point, FP.zero); + x = _x; y = _y; + } + + // Backdrop information. + /** @private */ private var _texture:BitmapData; + /** @private */ private var _textWidth:uint; + /** @private */ private var _textHeight:uint; + /** @private */ private var _repeatX:Boolean; + /** @private */ private var _repeatY:Boolean; + /** @private */ private var _x:Number; + /** @private */ private var _y:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Canvas.as b/src/net/flashpunk/graphics/Canvas.as new file mode 100644 index 0000000..b17b79d --- /dev/null +++ b/src/net/flashpunk/graphics/Canvas.as @@ -0,0 +1,313 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.display.Graphics; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + + /** + * A multi-purpose drawing canvas, can be sized beyond the normal Flash BitmapData limits. + */ + public class Canvas extends Graphic + { + /** + * Optional blend mode to use (see flash.display.BlendMode for blending modes). + */ + public var blend:String; + + /** + * Constructor. + * @param width Width of the canvas. + * @param height Height of the canvas. + */ + public function Canvas(width:uint, height:uint) + { + _width = width; + _height = height; + _refWidth = Math.ceil(width / _maxWidth); + _refHeight = Math.ceil(height / _maxHeight); + _ref = new BitmapData(_refWidth, _refHeight, false, 0); + var x:uint, y:uint, w:uint, h:uint, i:uint, + ww:uint = _width % _maxWidth, + hh:uint = _height % _maxHeight; + if (!ww) ww = _maxWidth; + if (!hh) hh = _maxHeight; + while (y < _refHeight) + { + h = y < _refHeight - 1 ? _maxHeight : hh; + while (x < _refWidth) + { + w = x < _refWidth - 1 ? _maxWidth : ww; + _ref.setPixel(x, y, i); + _buffers[i] = new BitmapData(w, h, true, 0); + i ++; x ++; + } + x = 0; y ++; + } + } + + /** @private Renders the canvas. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + // determine drawing location + _point.x = point.x + x - camera.x * scrollX; + _point.y = point.y + y - camera.y * scrollY; + + // render the buffers + var xx:int, yy:int, buffer:BitmapData, px:Number = _point.x; + while (yy < _refHeight) + { + while (xx < _refWidth) + { + buffer = _buffers[_ref.getPixel(xx, yy)]; + if (_tint || blend) + { + _matrix.tx = _point.x; + _matrix.ty = _point.y; + target.draw(buffer, _matrix, _tint, blend); + } + else target.copyPixels(buffer, buffer.rect, _point, null, null, true); + _point.x += _maxWidth; + xx ++; + } + _point.x = px; + _point.y += _maxHeight; + xx = 0; + yy ++; + } + } + + /** + * Draws to the canvas. + * @param x X position to draw. + * @param y Y position to draw. + * @param source Source BitmapData. + * @param rect Optional area of the source image to draw from. If null, the entire BitmapData will be drawn. + */ + public function draw(x:int, y:int, source:BitmapData, rect:Rectangle = null):void + { + var xx:int, yy:int; + for each (var buffer:BitmapData in _buffers) + { + _point.x = x - xx; + _point.y = y - yy; + buffer.copyPixels(source, rect ? rect : source.rect, _point, null, null, true); + xx += _maxWidth; + if (xx >= _width) + { + xx = 0; + yy += _maxHeight; + } + } + } + + /** + * Fills the rectangular area of the canvas. The previous contents of that area are completely removed. + * @param rect Fill rectangle. + * @param color Fill color. + * @param alpha Fill alpha. + */ + public function fill(rect:Rectangle, color:uint = 0, alpha:Number = 1):void + { + var xx:int, yy:int, buffer:BitmapData; + _rect.width = rect.width; + _rect.height = rect.height; + if (alpha >= 1) color |= 0xFF000000; + else if (alpha <= 0) color = 0; + else color = (uint(alpha * 255) << 24) | (0xFFFFFF & color); + for each (buffer in _buffers) + { + _rect.x = rect.x - xx; + _rect.y = rect.y - yy; + buffer.fillRect(_rect, color); + xx += _maxWidth; + if (xx >= _width) + { + xx = 0; + yy += _maxHeight; + } + } + } + + /** + * Draws over a rectangular area of the canvas. + * @param rect Drawing rectangle. + * @param color Draw color. + * @param alpha Draw alpha. If < 1, this rectangle will blend with existing contents of the canvas. + */ + public function drawRect(rect:Rectangle, color:uint = 0, alpha:Number = 1):void + { + var xx:int, yy:int, buffer:BitmapData; + if (alpha >= 1) + { + _rect.width = rect.width; + _rect.height = rect.height; + + for each (buffer in _buffers) + { + _rect.x = rect.x - xx; + _rect.y = rect.y - yy; + buffer.fillRect(_rect, 0xFF000000 | color); + xx += _maxWidth; + if (xx >= _width) + { + xx = 0; + yy += _maxHeight; + } + } + return; + } + for each (buffer in _buffers) + { + _graphics.clear(); + _graphics.beginFill(color, alpha); + _graphics.drawRect(rect.x - xx, rect.y - yy, rect.width, rect.height); + buffer.draw(FP.sprite); + xx += _maxWidth; + if (xx >= _width) + { + xx = 0; + yy += _maxHeight; + } + } + _graphics.endFill(); + } + + /** + * Fills the rectangle area of the canvas with the texture. + * @param rect Fill rectangle. + * @param texture Fill texture. + */ + public function fillTexture(rect:Rectangle, texture:BitmapData):void + { + var xx:int, yy:int; + for each (var buffer:BitmapData in _buffers) + { + _graphics.clear(); + _graphics.beginBitmapFill(texture); + _graphics.drawRect(rect.x - xx, rect.y - yy, rect.width, rect.height); + buffer.draw(FP.sprite); + xx += _maxWidth; + if (xx >= _width) + { + xx = 0; + yy += _maxHeight; + } + } + _graphics.endFill(); + } + + /** + * Draws the Graphic object to the canvas. + * @param x X position to draw. + * @param y Y position to draw. + * @param source Graphic to draw. + */ + public function drawGraphic(x:int, y:int, source:Graphic):void + { + var xx:int, yy:int; + for each (var buffer:BitmapData in _buffers) + { + _point.x = x - xx; + _point.y = y - yy; + source.render(buffer, _point, FP.zero); + xx += _maxWidth; + if (xx >= _width) + { + xx = 0; + yy += _maxHeight; + } + } + } + + /** + * The tinted color of the Canvas. Use 0xFFFFFF to draw the it normally. + */ + public function get color():uint { return _color; } + public function set color(value:uint):void + { + value %= 0xFFFFFF; + if (_color == value) return; + _color = value; + if (_alpha == 1 && _color == 0xFFFFFF) + { + _tint = null; + return; + } + _tint = _colorTransform; + _tint.redMultiplier = (_color >> 16 & 0xFF) / 255; + _tint.greenMultiplier = (_color >> 8 & 0xFF) / 255; + _tint.blueMultiplier = (_color & 0xFF) / 255; + _tint.alphaMultiplier = _alpha; + } + + /** + * Change the opacity of the Canvas, a value from 0 to 1. + */ + public function get alpha():Number { return _alpha; } + public function set alpha(value:Number):void + { + if (value < 0) value = 0; + if (value > 1) value = 1; + if (_alpha == value) return; + _alpha = value; + if (_alpha == 1 && _color == 0xFFFFFF) + { + _tint = null; + return; + } + _tint = _colorTransform; + _tint.redMultiplier = (_color >> 16 & 0xFF) / 255; + _tint.greenMultiplier = (_color >> 8 & 0xFF) / 255; + _tint.blueMultiplier = (_color & 0xFF) / 255; + _tint.alphaMultiplier = _alpha; + } + + /** + * Shifts the canvas' pixels by the offset. + * @param x Horizontal shift. + * @param y Vertical shift. + */ + public function shift(x:int = 0, y:int = 0):void + { + drawGraphic(x, y, this); + } + + /** + * Width of the canvas. + */ + public function get width():uint { return _width; } + + /** + * Height of the canvas. + */ + public function get height():uint { return _height; } + + // Buffer information. + /** @private */ private var _buffers:Vector. = new Vector.; + /** @private */ protected var _width:uint; + /** @private */ protected var _height:uint; + /** @private */ protected var _maxWidth:uint = 4000; + /** @private */ protected var _maxHeight:uint = 4000; + + // Color tinting information. + /** @private */ private var _color:uint = 0xFFFFFF; + /** @private */ private var _alpha:Number = 1; + /** @private */ private var _tint:ColorTransform; + /** @private */ private var _colorTransform:ColorTransform = new ColorTransform; + /** @private */ private var _matrix:Matrix = new Matrix; + + // Canvas reference information. + /** @private */ private var _ref:BitmapData; + /** @private */ private var _refWidth:uint; + /** @private */ private var _refHeight:uint; + + // Global objects. + /** @private */ private var _rect:Rectangle = new Rectangle; + /** @private */ private var _graphics:Graphics = FP.sprite.graphics; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Emitter.as b/src/net/flashpunk/graphics/Emitter.as new file mode 100644 index 0000000..409940f --- /dev/null +++ b/src/net/flashpunk/graphics/Emitter.as @@ -0,0 +1,260 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.ColorTransform; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + import net.flashpunk.utils.Input; + import net.flashpunk.utils.Key; + + /** + * Particle emitter used for emitting and rendering particle sprites. + * Good rendering performance with large amounts of particles. + */ + public class Emitter extends Graphic + { + /** + * Constructor. Sets the source image to use for newly added particle types. + * @param source Source image. + * @param frameWidth Frame width. + * @param frameHeight Frame height. + */ + public function Emitter(source:*, frameWidth:uint = 0, frameHeight:uint = 0) + { + setSource(source, frameWidth, frameHeight); + active = true; + } + + /** + * Changes the source image to use for newly added particle types. + * @param source Source image. + * @param frameWidth Frame width. + * @param frameHeight Frame height. + */ + public function setSource(source:*, frameWidth:uint = 0, frameHeight:uint = 0):void + { + if (source is Class) _source = FP.getBitmap(source); + else if (source is BitmapData) _source = source; + if (!_source) throw new Error("Invalid source image."); + _width = _source.width; + _height = _source.height; + _frameWidth = frameWidth ? frameWidth : _width; + _frameHeight = frameHeight ? frameHeight : _height; + _frameCount = uint(_width / _frameWidth) * uint(_height / _frameHeight); + } + + override public function update():void + { + // quit if there are no particles + if (!_particle) return; + + // particle info + var e:Number = FP.fixed ? 1 : FP.elapsed, + p:Particle = _particle, + n:Particle, t:Number; + + // loop through the particles + while (p) + { + // update time scale + p._time += e; + t = p._time / p._duration; + + // remove on time-out + if (p._time >= p._duration) + { + if (p._next) p._next._prev = p._prev; + if (p._prev) p._prev._next = p._next; + else _particle = p._next; + n = p._next; + p._next = _cache; + p._prev = null; + _cache = p; + p = n; + _particleCount --; + continue; + } + + // get next particle + p = p._next; + } + } + + /** @private Renders the particles. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + // quit if there are no particles + if (!_particle) return; + + // get rendering position + _point.x = point.x + x - camera.x * scrollX; + _point.y = point.y + y - camera.y * scrollY; + + // particle info + var t:Number, td:Number, + p:Particle = _particle, + type:ParticleType, + rect:Rectangle; + + // loop through the particles + while (p) + { + // get time scale + t = p._time / p._duration; + + // get particle type + type = p._type; + rect = type._frame; + + // get position + td = (type._ease == null) ? t : type._ease(t); + _p.x = _point.x + p._x + p._moveX * td; + _p.y = _point.y + p._y + p._moveY * td; + + // get frame + rect.x = rect.width * type._frames[uint(td * type._frameCount)]; + rect.y = uint(rect.x / type._width) * rect.height; + rect.x %= type._width; + + // draw particle + if (type._buffer) + { + // get alpha + _tint.alphaMultiplier = type._alpha + type._alphaRange * ((type._alphaEase == null) ? t : type._alphaEase(t)); + + // get color + td = (type._colorEase == null) ? t : type._colorEase(t); + _tint.redMultiplier = type._red + type._redRange * td; + _tint.greenMultiplier = type._green + type._greenRange * td; + _tint.blueMultiplier = type._blue + type._blueRange * td; + type._buffer.fillRect(type._bufferRect, 0); + type._buffer.copyPixels(type._source, rect, FP.zero); + type._buffer.colorTransform(type._bufferRect, _tint); + + // draw particle + target.copyPixels(type._buffer, type._bufferRect, _p, null, null, true); + } + else target.copyPixels(type._source, rect, _p, null, null, true); + + // get next particle + p = p._next; + } + } + + /** + * Creates a new Particle type for this Emitter. + * @param name Name of the particle type. + * @param frames Array of frame indices for the particles to animate. + * @return A new ParticleType object. + */ + public function newType(name:String, frames:Array = null):ParticleType + { + if (_types[name]) throw new Error("Cannot add multiple particle types of the same name"); + return (_types[name] = new ParticleType(name, frames, _source, _frameWidth, _frameHeight)); + } + + /** + * Defines the motion range for a particle type. + * @param name The particle type. + * @param angle Launch Direction. + * @param distance Distance to travel. + * @param duration Particle duration. + * @param angleRange Random amount to add to the particle's direction. + * @param distanceRange Random amount to add to the particle's distance. + * @param durationRange Random amount to add to the particle's duration. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setMotion(name:String, angle:Number, distance:Number, duration:Number, angleRange:Number = 0, distanceRange:Number = 0, durationRange:Number = 0, ease:Function = null):ParticleType + { + return (_types[name] as ParticleType).setMotion(angle, distance, duration, angleRange, distanceRange, durationRange, ease); + } + + /** + * Sets the alpha range of the particle type. + * @param name The particle type. + * @param start The starting alpha. + * @param finish The finish alpha. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setAlpha(name:String, start:Number = 1, finish:Number = 0, ease:Function = null):ParticleType + { + return (_types[name] as ParticleType).setAlpha(start, finish, ease); + } + + /** + * Sets the color range of the particle type. + * @param name The particle type. + * @param start The starting color. + * @param finish The finish color. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setColor(name:String, start:uint = 0xFFFFFF, finish:uint = 0, ease:Function = null):ParticleType + { + return (_types[name] as ParticleType).setColor(start, finish, ease); + } + + /** + * Emits a particle. + * @param name Particle type to emit. + * @param x X point to emit from. + * @param y Y point to emit from. + * @return + */ + public function emit(name:String, x:Number, y:Number):Particle + { + if (!_types[name]) throw new Error("Particle type \"" + name + "\" does not exist."); + var p:Particle, type:ParticleType = _types[name]; + + if (_cache) + { + p = _cache; + _cache = p._next; + } + else p = new Particle; + p._next = _particle; + p._prev = null; + if (p._next) p._next._prev = p; + + p._type = type; + p._time = 0; + p._duration = type._duration + type._durationRange * FP.random; + var a:Number = type._angle + type._angleRange * FP.random, + d:Number = type._distance + type._distanceRange * FP.random; + p._moveX = Math.cos(a) * d; + p._moveY = Math.sin(a) * d; + p._x = x; + p._y = y; + _particleCount ++; + return (_particle = p); + } + + /** + * Amount of currently existing particles. + */ + public function get particleCount():uint { return _particleCount; } + + // Particle infromation. + /** @private */ private var _types:Object = { }; + /** @private */ private var _particle:Particle; + /** @private */ private var _cache:Particle; + /** @private */ private var _particleCount:uint; + + // Source information. + /** @private */ private var _source:BitmapData; + /** @private */ private var _width:uint; + /** @private */ private var _height:uint; + /** @private */ private var _frameWidth:uint; + /** @private */ private var _frameHeight:uint; + /** @private */ private var _frameCount:uint; + + // Drawing information. + /** @private */ private var _p:Point = new Point; + /** @private */ private var _tint:ColorTransform = new ColorTransform; + /** @private */ private static const SIN:Number = Math.PI / 2; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Graphiclist.as b/src/net/flashpunk/graphics/Graphiclist.as new file mode 100644 index 0000000..d0b389e --- /dev/null +++ b/src/net/flashpunk/graphics/Graphiclist.as @@ -0,0 +1,142 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.utils.Dictionary; + import net.flashpunk.*; + + /** + * A Graphic that can contain multiple Graphics of one or various types. + * Useful for drawing sprites with multiple different parts, etc. + */ + public class Graphiclist extends Graphic + { + /** + * Constructor. + * @param ...graphic Graphic objects to add to the list. + */ + public function Graphiclist(...graphic) + { + for each (var g:Graphic in graphic) add(g); + } + + /** @private Updates the graphics in the list. */ + override public function update():void + { + for each (var g:Graphic in _graphics) + { + if (g.active) g.update(); + } + } + + /** @private Renders the Graphics in the list. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + point.x += x; + point.y += y; + camera.x *= scrollX; + camera.y *= scrollY; + for each (var g:Graphic in _graphics) + { + if (g.visible) + { + if (g.relative) + { + _point.x = point.x; + _point.y = point.y; + } + else _point.x = _point.y = 0; + _camera.x = camera.x; + _camera.y = camera.y; + g.render(target, _point, _camera); + } + } + } + + /** + * Adds the Graphic to the list. + * @param graphic The Graphic to add. + * @return The added Graphic. + */ + public function add(graphic:Graphic):Graphic + { + _graphics[_count ++] = graphic; + if (!active) active = graphic.active; + return graphic; + } + + /** + * Removes the Graphic from the list. + * @param graphic The Graphic to remove. + * @return The removed Graphic. + */ + public function remove(graphic:Graphic):Graphic + { + if (_graphics.indexOf(graphic) < 0) return graphic; + _temp.length = 0; + for each (var g:Graphic in _graphics) + { + if (g == graphic) _count --; + else _temp[_temp.length] = g; + } + var temp:Vector. = _graphics; + _graphics = _temp; + _temp = temp; + updateCheck(); + return graphic; + } + + /** + * Removes the Graphic from the position in the list. + * @param index Index to remove. + */ + public function removeAt(index:uint = 0):void + { + if (!_graphics.length) return; + index %= _graphics.length; + remove(_graphics[index % _graphics.length]); + updateCheck(); + } + + /** + * Removes all Graphics from the list. + */ + public function removeAll():void + { + _graphics.length = _temp.length = _count = 0; + active = false; + } + + /** + * All Graphics in this list. + */ + public function get children():Vector. { return _graphics; } + + /** + * Amount of Graphics in this list. + */ + public function get count():uint { return _count; } + + /** + * Check if the Graphiclist should update. + */ + private function updateCheck():void + { + active = false; + for each (var g:Graphic in _graphics) + { + if (g.active) + { + active = true; + return; + } + } + } + + // List information. + /** @private */ private var _graphics:Vector. = new Vector.; + /** @private */ private var _temp:Vector. = new Vector.; + /** @private */ private var _count:uint; + /** @private */ private var _camera:Point = new Point; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Image.as b/src/net/flashpunk/graphics/Image.as new file mode 100644 index 0000000..275e6f5 --- /dev/null +++ b/src/net/flashpunk/graphics/Image.as @@ -0,0 +1,312 @@ +package net.flashpunk.graphics +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.*; + + /** + * Performance-optimized non-animated image. Can be drawn to the screen with transformations. + */ + public class Image extends Graphic + { + /** + * Rotation of the image, in degrees. + */ + public var angle:Number = 0; + + /** + * Scale of the image, effects both x and y scale. + */ + public var scale:Number = 1; + + /** + * X scale of the image. + */ + public var scaleX:Number = 1; + + /** + * Y scale of the image. + */ + public var scaleY:Number = 1; + + /** + * X origin of the image, determines transformation point. + */ + public var originX:int; + + /** + * Y origin of the image, determines transformation point. + */ + public var originY:int; + + /** + * Optional blend mode to use when drawing this image. + * Use constants from the flash.display.BlendMode class. + */ + public var blend:String; + + /** + * If the image should be drawn transformed with pixel smoothing. + * This will affect drawing performance, but look less pixelly. + */ + public var smooth:Boolean; + + /** + * Constructor. + * @param source Source image. + * @param clipRect Optional rectangle defining area of the source image to draw. + */ + public function Image(source:* = null, clipRect:Rectangle = null) + { + if (source is Class) + { + _source = FP.getBitmap(source); + _class = String(source); + } + else if (source is BitmapData) _source = source; + if (!_source) throw new Error("Invalid source image."); + _sourceRect = _source.rect; + if (clipRect) + { + if (!clipRect.width) clipRect.width = _sourceRect.width; + if (!clipRect.height) clipRect.height = _sourceRect.height; + _sourceRect = clipRect; + } + createBuffer(); + updateBuffer(); + } + + /** @private Creates the buffer. */ + protected function createBuffer():void + { + _buffer = new BitmapData(_sourceRect.width, _sourceRect.height, true, 0); + _bufferRect = _buffer.rect; + _bitmap.bitmapData = _buffer; + } + + /** @private Renders the image. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + // quit if no graphic is assigned + if (!_buffer) return; + + // determine drawing location + _point.x = point.x + x - camera.x * scrollX; + _point.y = point.y + y - camera.y * scrollY; + + // render without transformation + if (angle == 0 && scaleX * scale == 1 && scaleY * scale == 1 && !blend) + { + target.copyPixels(_buffer, _bufferRect, _point, null, null, true); + return; + } + + // render with transformation + _matrix.b = _matrix.c = 0; + _matrix.a = scaleX * scale; + _matrix.d = scaleY * scale; + _matrix.tx = -originX * _matrix.a; + _matrix.ty = -originY * _matrix.d; + if (angle != 0) _matrix.rotate(angle * FP.RAD); + _matrix.tx += originX + _point.x; + _matrix.ty += originY + _point.y; + target.draw(_bitmap, _matrix, null, blend, null, smooth); + } + + /** + * Creates a new rectangle Image. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @param color Color of the rectangle. + * @return A new Image object. + */ + public static function createRect(width:uint, height:uint, color:uint = 0xFFFFFF):Image + { + var source:BitmapData = new BitmapData(width, height, true, 0xFF000000 | color); + return new Image(source); + } + + /** + * Creates a new circle Image. + * @param radius Radius of the circle. + * @param color Color of the circle. + * @return A new Circle object. + */ + public static function createCircle(radius:uint, color:uint = 0xFFFFFF):Image + { + FP.sprite.graphics.clear(); + FP.sprite.graphics.beginFill(color); + FP.sprite.graphics.drawCircle(radius, radius, radius); + var data:BitmapData = new BitmapData(radius * 2, radius * 2, true, 0); + data.draw(FP.sprite); + return new Image(data); + } + + /** + * Updates the image buffer. + */ + public function updateBuffer(clearBefore:Boolean = false):void + { + if (!_source) return; + if (clearBefore) _buffer.fillRect(_bufferRect, 0); + _buffer.copyPixels(_source, _sourceRect, FP.zero); + if (_tint) _buffer.colorTransform(_bufferRect, _tint); + } + + /** + * Clears the image buffer. + */ + public function clear():void + { + _buffer.fillRect(_bufferRect, 0); + } + + /** + * Change the opacity of the Image, a value from 0 to 1. + */ + public function get alpha():Number { return _alpha; } + public function set alpha(value:Number):void + { + value = value < 0 ? 0 : (value > 1 ? 1 : value); + if (_alpha == value) return; + _alpha = value; + if (_alpha == 1 && _color == 0xFFFFFF) + { + _tint = null; + return updateBuffer(); + } + _tint = _colorTransform; + _tint.redMultiplier = (_color >> 16 & 0xFF) / 255; + _tint.greenMultiplier = (_color >> 8 & 0xFF) / 255; + _tint.blueMultiplier = (_color & 0xFF) / 255; + _tint.alphaMultiplier = _alpha; + updateBuffer(); + } + + /** + * The tinted color of the Image. Use 0xFFFFFF to draw the Image normally. + */ + public function get color():uint { return _color; } + public function set color(value:uint):void + { + value &= 0xFFFFFF; + if (_color == value) return; + _color = value; + if (_alpha == 1 && _color == 0xFFFFFF) + { + _tint = null; + return updateBuffer(); + } + _tint = _colorTransform; + _tint.redMultiplier = (_color >> 16 & 0xFF) / 255; + _tint.greenMultiplier = (_color >> 8 & 0xFF) / 255; + _tint.blueMultiplier = (_color & 0xFF) / 255; + _tint.alphaMultiplier = _alpha; + updateBuffer(); + } + + /** + * If you want to draw the Image horizontally flipped. This is + * faster than setting scaleX to -1 if your image isn't transformed. + */ + public function get flipped():Boolean { return _flipped; } + public function set flipped(value:Boolean):void + { + if (_flipped == value || !_class) return; + _flipped = value; + var temp:BitmapData = _source; + if (!value || _flip) + { + _source = _flip; + _flip = temp; + return updateBuffer(); + } + if (_flips[_class]) + { + _source = _flips[_class]; + _flip = temp; + return updateBuffer(); + } + _source = _flips[_class] = new BitmapData(_source.width, _source.height, true, 0); + _flip = temp; + FP.matrix.identity(); + FP.matrix.a = -1; + FP.matrix.tx = _source.width; + _source.draw(temp, FP.matrix); + updateBuffer(); + } + + /** + * Centers the Image's originX/Y to its center. + */ + public function centerOrigin():void + { + originX = _bufferRect.width / 2; + originY = _bufferRect.height / 2; + } + + /** + * Centers the Image's originX/Y to its center, and negates the offset by the same amount. + */ + public function centerOO():void + { + x += originX; + y += originY; + centerOrigin(); + x -= originX; + y -= originY; + } + + /** + * Width of the image. + */ + public function get width():uint { return _bufferRect.width; } + + /** + * Height of the image. + */ + public function get height():uint { return _bufferRect.height; } + + /** + * The scaled width of the image. + */ + public function get scaledWidth():uint { return width * scaleX * scale; } + + /** + * The scaled height of the image. + */ + public function get scaledHeight():uint { return height * scaleY * scale; } + + /** + * Clipping rectangle for the image. + */ + public function get clipRect():Rectangle { return _sourceRect; } + + /** @private Source BitmapData image. */ + protected function get source():BitmapData { return _source; } + + // Source and buffer information. + /** @private */ protected var _source:BitmapData; + /** @private */ protected var _sourceRect:Rectangle; + /** @private */ protected var _buffer:BitmapData; + /** @private */ protected var _bufferRect:Rectangle; + /** @private */ protected var _bitmap:Bitmap = new Bitmap; + + // Color and alpha information. + /** @private */ private var _alpha:Number = 1; + /** @private */ private var _color:uint = 0x00FFFFFF; + /** @private */ protected var _tint:ColorTransform; + /** @private */ private var _colorTransform:ColorTransform = new ColorTransform; + /** @private */ private var _matrix:Matrix = FP.matrix; + + // Flipped image information. + /** @private */ private var _class:String; + /** @private */ protected var _flipped:Boolean; + /** @private */ private var _flip:BitmapData; + /** @private */ private static var _flips:Object = { }; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Particle.as b/src/net/flashpunk/graphics/Particle.as new file mode 100644 index 0000000..5201274 --- /dev/null +++ b/src/net/flashpunk/graphics/Particle.as @@ -0,0 +1,35 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + + /** + * Used by the Emitter class to track an existing Particle. + */ + public class Particle + { + /** + * Constructor. + */ + public function Particle() + { + + } + + // Particle information. + /** @private */ internal var _type:ParticleType; + /** @private */ internal var _time:Number; + /** @private */ internal var _duration:Number; + + // Motion information. + /** @private */ internal var _x:Number; + /** @private */ internal var _y:Number; + /** @private */ internal var _moveX:Number; + /** @private */ internal var _moveY:Number; + + // List information. + /** @private */ internal var _prev:Particle; + /** @private */ internal var _next:Particle; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/ParticleType.as b/src/net/flashpunk/graphics/ParticleType.as new file mode 100644 index 0000000..076cec2 --- /dev/null +++ b/src/net/flashpunk/graphics/ParticleType.as @@ -0,0 +1,157 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.Rectangle; + import net.flashpunk.FP; + + /** + * Template used to define a particle type used by the Emitter class. Instead + * of creating this object yourself, fetch one with Emitter's add() function. + */ + public class ParticleType + { + /** + * Constructor. + * @param name Name of the particle type. + * @param frames Array of frame indices to animate through. + * @param source Source image. + * @param frameWidth Frame width. + * @param frameHeight Frame height. + * @param frameCount Frame count. + */ + public function ParticleType(name:String, frames:Array, source:BitmapData, frameWidth:uint, frameHeight:uint) + { + _name = name; + _source = source; + _width = source.width; + _frame = new Rectangle(0, 0, frameWidth, frameHeight); + _frames = frames; + _frameCount = frames.length; + } + + /** + * Defines the motion range for this particle type. + * @param angle Launch Direction. + * @param distance Distance to travel. + * @param duration Particle duration. + * @param angleRange Random amount to add to the particle's direction. + * @param distanceRange Random amount to add to the particle's distance. + * @param durationRange Random amount to add to the particle's duration. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setMotion(angle:Number, distance:Number, duration:Number, angleRange:Number = 0, distanceRange:Number = 0, durationRange:Number = 0, ease:Function = null):ParticleType + { + _angle = angle * FP.RAD; + _distance = distance; + _duration = duration; + _angleRange = angleRange * FP.RAD; + _distanceRange = distanceRange; + _durationRange = durationRange; + _ease = ease; + return this; + } + + /** + * Defines the motion range for this particle type based on the vector. + * @param x X distance to move. + * @param y Y distance to move. + * @param duration Particle duration. + * @param durationRange Random amount to add to the particle's duration. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setMotionVector(x:Number, y:Number, duration:Number, durationRange:Number = 0, ease:Function = null):ParticleType + { + _angle = Math.atan2(y, x); + _angleRange = 0; + _duration = duration; + _durationRange = durationRange; + _ease = ease; + return this; + } + + /** + * Sets the alpha range of this particle type. + * @param start The starting alpha. + * @param finish The finish alpha. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setAlpha(start:Number = 1, finish:Number = 0, ease:Function = null):ParticleType + { + start = start < 0 ? 0 : (start > 1 ? 1 : start); + finish = finish < 0 ? 0 : (finish > 1 ? 1 : finish); + _alpha = start; + _alphaRange = finish - start; + _alphaEase = ease; + createBuffer(); + return this; + } + + /** + * Sets the color range of this particle type. + * @param start The starting color. + * @param finish The finish color. + * @param ease Optional easer function. + * @return This ParticleType object. + */ + public function setColor(start:uint = 0xFFFFFF, finish:uint = 0, ease:Function = null):ParticleType + { + start &= 0xFFFFFF; + finish &= 0xFFFFFF; + _red = (start >> 16 & 0xFF) / 255; + _green = (start >> 8 & 0xFF) / 255; + _blue = (start & 0xFF) / 255; + _redRange = (finish >> 16 & 0xFF) / 255 - _red; + _greenRange = (finish >> 8 & 0xFF) / 255 - _green; + _blueRange = (finish & 0xFF) / 255 - _blue; + _colorEase = ease; + createBuffer(); + return this; + } + + /** @private Creates the buffer if it doesn't exist. */ + private function createBuffer():void + { + if (_buffer) return; + _buffer = new BitmapData(_frame.width, _frame.height, true, 0); + _bufferRect = _buffer.rect; + } + + // Particle information. + /** @private */ internal var _name:String; + /** @private */ internal var _source:BitmapData; + /** @private */ internal var _width:uint; + /** @private */ internal var _frame:Rectangle; + /** @private */ internal var _frames:Array; + /** @private */ internal var _frameCount:uint; + + // Motion information. + /** @private */ internal var _angle:Number; + /** @private */ internal var _angleRange:Number; + /** @private */ internal var _distance:Number; + /** @private */ internal var _distanceRange:Number; + /** @private */ internal var _duration:Number; + /** @private */ internal var _durationRange:Number; + /** @private */ internal var _ease:Function; + + // Alpha information. + /** @private */ internal var _alpha:Number = 1; + /** @private */ internal var _alphaRange:Number = 0; + /** @private */ internal var _alphaEase:Function; + + // Color information. + /** @private */ internal var _red:Number = 1; + /** @private */ internal var _redRange:Number = 0; + /** @private */ internal var _green:Number = 1; + /** @private */ internal var _greenRange:Number = 0; + /** @private */ internal var _blue:Number = 1; + /** @private */ internal var _blueRange:Number = 0; + /** @private */ internal var _colorEase:Function; + + // Buffer information. + /** @private */ internal var _buffer:BitmapData; + /** @private */ internal var _bufferRect:Rectangle; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/PreRotation.as b/src/net/flashpunk/graphics/PreRotation.as new file mode 100644 index 0000000..c368fab --- /dev/null +++ b/src/net/flashpunk/graphics/PreRotation.as @@ -0,0 +1,106 @@ +package net.flashpunk.graphics +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.Dictionary; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + + /** + * Creates a pre-rotated Image strip to increase runtime performance for rotating graphics. + */ + public class PreRotation extends Image + { + /** + * Current angle to fetch the pre-rotated frame from. + */ + public var frameAngle:Number = 0; + + /** + * Constructor. + * @param source The source image to be rotated. + * @param frameCount How many frames to use. More frames result in smoother rotations. + * @param smooth Make the rotated graphic appear less pixelly. + */ + public function PreRotation(source:Class, frameCount:uint = 36, smooth:Boolean = false) + { + var r:BitmapData = _rotated[source]; + _frame = new Rectangle(0, 0, _size[source], _size[source]); + if (!r) + { + // produce a rotated bitmap strip + var temp:BitmapData = (new source).bitmapData, + size:uint = _size[source] = Math.ceil(FP.distance(0, 0, temp.width, temp.height)); + _frame.width = _frame.height = size; + var width:uint = _frame.width * frameCount, + height:uint = _frame.height; + if (width > _MAX_WIDTH) + { + width = _MAX_WIDTH - (_MAX_WIDTH % _frame.width); + height = Math.ceil(frameCount / (width / _frame.width)) * _frame.height; + } + r = new BitmapData(width, height, true, 0); + var m:Matrix = FP.matrix, + a:Number = 0, + aa:Number = (Math.PI * 2) / -frameCount, + ox:uint = temp.width / 2, + oy:uint = temp.height / 2, + o:uint = _frame.width / 2, + x:uint = 0, + y:uint = 0; + while (y < height) + { + while (x < width) + { + m.identity(); + m.translate(-ox, -oy); + m.rotate(a); + m.translate(o + x, o + y); + r.draw(temp, m, null, null, null, smooth); + x += _frame.width; + a += aa; + } + x = 0; + y += _frame.height; + } + } + _source = r; + _width = r.width; + _frameCount = frameCount; + super(_source, _frame); + } + + /** @private Renders the PreRotated graphic. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + frameAngle %= 360; + if (frameAngle < 0) frameAngle += 360; + _current = uint(_frameCount * (frameAngle / 360)); + if (_last != _current) + { + _last = _current; + _frame.x = _frame.width * _last; + _frame.y = uint(_frame.x / _width) * _frame.height; + _frame.x %= _width; + updateBuffer(); + } + super.render(target, point, camera); + } + + // Rotation information. + /** @private */ private var _width:uint; + /** @private */ private var _frame:Rectangle; + /** @private */ private var _frameCount:uint; + /** @private */ private var _last:int = -1; + /** @private */ private var _current:int = -1; + + // Global information. + /** @private */ private static var _rotated:Dictionary = new Dictionary; + /** @private */ private static var _size:Dictionary = new Dictionary; + /** @private */ private static const _MAX_WIDTH:uint = 4000; + /** @private */ private static const _MAX_HEIGHT:uint = 4000; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Spritemap.as b/src/net/flashpunk/graphics/Spritemap.as new file mode 100644 index 0000000..2fd2d3c --- /dev/null +++ b/src/net/flashpunk/graphics/Spritemap.as @@ -0,0 +1,252 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.display.SpreadMethod; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.FP; + + /** + * Performance-optimized animated Image. Can have multiple animations, + * which draw frames from the provided source image to the screen. + */ + public class Spritemap extends Image + { + /** + * If the animation has stopped. + */ + public var complete:Boolean = true; + + /** + * Optional callback function for animation end. + */ + public var callback:Function; + + /** + * Animation speed factor, alter this to speed up/slow down all animations. + */ + public var rate:Number = 1; + + /** + * Constructor. + * @param source Source image. + * @param frameWidth Frame width. + * @param frameHeight Frame height. + * @param callback Optional callback function for animation end. + */ + public function Spritemap(source:*, frameWidth:uint = 0, frameHeight:uint = 0, callback:Function = null) + { + _rect = new Rectangle(0, 0, frameWidth, frameHeight); + super(source, _rect); + if (!frameWidth) _rect.width = this.source.width; + if (!frameHeight) _rect.height = this.source.height; + _width = this.source.width; + _height = this.source.height; + _columns = _width / _rect.width; + _rows = _height / _rect.height; + _frameCount = _columns * _rows; + this.callback = callback; + updateBuffer(); + active = true; + } + + /** + * Updates the spritemap's buffer. + */ + override public function updateBuffer(clearBefore:Boolean = false):void + { + // get position of the current frame + _rect.x = _rect.width * _frame; + _rect.y = uint(_rect.x / _width) * _rect.height; + _rect.x %= _width; + if (_flipped) _rect.x = (_width - _rect.width) - _rect.x; + + // update the buffer + super.updateBuffer(clearBefore); + } + + /** @private Updates the animation. */ + override public function update():void + { + if (_anim && !complete) + { + _timer += (FP.fixed ? _anim._frameRate : _anim._frameRate * FP.elapsed) * rate; + if (_timer >= 1) + { + while (_timer >= 1) + { + _timer --; + _index ++; + if (_index == _anim._frameCount) + { + if (_anim._loop) + { + _index = 0; + if (callback != null) callback(); + } + else + { + _index = _anim._frameCount - 1; + complete = true; + if (callback != null) callback(); + break; + } + } + } + if (_anim) _frame = uint(_anim._frames[_index]); + updateBuffer(); + } + } + } + + /** + * Add an Animation. + * @param name Name of the animation. + * @param frames Array of frame indices to animate through. + * @param frameRate Animation speed. + * @param loop If the animation should loop. + * @return A new Anim object for the animation. + */ + public function add(name:String, frames:Array, frameRate:Number = 0, loop:Boolean = true):Anim + { + if (_anims[name]) throw new Error("Cannot have multiple animations with the same name"); + (_anims[name] = new Anim(name, frames, frameRate, loop))._parent = this; + return _anims[name]; + } + + /** + * Plays an animation. + * @param name Name of the animation to play. + * @param reset If the animation should force-restart if it is already playing. + * @return Anim object representing the played animation. + */ + public function play(name:String = "", reset:Boolean = false):Anim + { + if (!reset && _anim && _anim._name == name) return _anim; + _anim = _anims[name]; + if (!_anim) + { + _frame = _index = 0; + complete = true; + updateBuffer(); + return null; + } + _index = 0; + _timer = 0; + _frame = uint(_anim._frames[0]); + complete = false; + updateBuffer(); + return _anim; + } + + /** + * Gets the frame index based on the column and row of the source image. + * @param column Frame column. + * @param row Frame row. + * @return Frame index. + */ + public function getFrame(column:uint = 0, row:uint = 0):uint + { + return (row % _rows) * _columns + (column % _columns); + } + + /** + * Sets the current display frame based on the column and row of the source image. + * When you set the frame, any animations playing will be stopped to force the frame. + * @param column Frame column. + * @param row Frame row. + */ + public function setFrame(column:uint = 0, row:uint = 0):void + { + _anim = null; + var frame:uint = (row % _rows) * _columns + (column % _columns); + if (_frame == frame) return; + _frame = frame; + updateBuffer(); + } + + /** + * Assigns the Spritemap to a random frame. + */ + public function randFrame():void + { + frame = FP.rand(_frameCount); + } + + /** + * Sets the frame to the frame index of an animation. + * @param name Animation to draw the frame frame. + * @param index Index of the frame of the animation to set to. + */ + public function setAnimFrame(name:String, index:int):void + { + var frames:Array = _anims[name]._frames; + index %= frames.length; + if (index < 0) index += frames.length; + frame = frames[index]; + } + + /** + * Sets the current frame index. When you set this, any + * animations playing will be stopped to force the frame. + */ + public function get frame():int { return _frame; } + public function set frame(value:int):void + { + _anim = null; + value %= _frameCount; + if (value < 0) value = _frameCount + value; + if (_frame == value) return; + _frame = value; + updateBuffer(); + } + + /** + * Current index of the playing animation. + */ + public function get index():uint { return _anim ? _index : 0; } + public function set index(value:uint):void + { + if (!_anim) return; + value %= _anim._frameCount; + if (_index == value) return; + _index = value; + _frame = uint(_anim._frames[_index]); + updateBuffer(); + } + + /** + * The amount of frames in the Spritemap. + */ + public function get frameCount():uint { return _frameCount; } + + /** + * Columns in the Spritemap. + */ + public function get columns():uint { return _columns; } + + /** + * Rows in the Spritemap. + */ + public function get rows():uint { return _rows; } + + /** + * The currently playing animation. + */ + public function get currentAnim():String { return _anim ? _anim._name : ""; } + + // Spritemap information. + /** @private */ protected var _rect:Rectangle; + /** @private */ protected var _width:uint; + /** @private */ protected var _height:uint; + /** @private */ private var _columns:uint; + /** @private */ private var _rows:uint; + /** @private */ private var _frameCount:uint; + /** @private */ private var _anims:Object = { }; + /** @private */ private var _anim:Anim; + /** @private */ private var _index:uint; + /** @private */ protected var _frame:uint; + /** @private */ private var _timer:Number = 0; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Stamp.as b/src/net/flashpunk/graphics/Stamp.as new file mode 100644 index 0000000..a20aa43 --- /dev/null +++ b/src/net/flashpunk/graphics/Stamp.as @@ -0,0 +1,56 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.*; + + /** + * A simple non-transformed, non-animated graphic. + */ + public class Stamp extends Graphic + { + /** + * Constructor. + * @param source Source image. + * @param x X offset. + * @param y Y offset. + */ + public function Stamp(source:*, x:int = 0, y:int = 0) + { + // set the origin + this.x = x; + this.y = y; + + // set the graphic + if (!source) return; + if (source is Class) _source = FP.getBitmap(source); + else if (source is BitmapData) _source = source; + if (_source) _sourceRect = _source.rect; + } + + /** @private Renders the Graphic. */ + override public function render(target:BitmapData, point:Point, camera:Point):void + { + if (!_source) return; + _point.x = point.x + x - camera.x * scrollX; + _point.y = point.y + y - camera.y * scrollY; + target.copyPixels(_source, _sourceRect, _point, null, null, true); + } + + /** + * Source BitmapData image. + */ + public function get source():BitmapData { return _source; } + public function set source(value:BitmapData):void + { + _source = value; + if (_source) _sourceRect = _source.rect; + } + + // Stamp information. + /** @private */ private var _source:BitmapData; + /** @private */ private var _sourceRect:Rectangle; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Text.as b/src/net/flashpunk/graphics/Text.as new file mode 100644 index 0000000..4e3160a --- /dev/null +++ b/src/net/flashpunk/graphics/Text.as @@ -0,0 +1,126 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.TextField; + import flash.text.TextFormat; + import flash.text.TextLineMetrics; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + + /** + * Used for drawing text using embedded fonts. + */ + public class Text extends Image + { + /** + * The font to assign to new Text objects. + */ + public static var font:String = "default"; + + /** + * The font size to assign to new Text objects. + */ + public static var size:uint = 16; + + /** + * Constructor. + * @param text Text to display. + * @param x X offset. + * @param y Y offset. + * @param width Image width (leave as 0 to size to the starting text string). + * @param height Image height (leave as 0 to size to the starting text string). + */ + public function Text(text:String, x:Number = 0, y:Number = 0, width:uint = 0, height:uint = 0) + { + _field.embedFonts = true; + _field.defaultTextFormat = _form = new TextFormat(Text.font, Text.size, 0xFFFFFF); + _field.text = _text = text; + if (!width) width = _field.textWidth + 4; + if (!height) height = _field.textHeight + 4; + _source = new BitmapData(width, height, true, 0); + super(_source); + updateBuffer(); + this.x = x; + this.y = y; + } + + /** @private Updates the drawing buffer. */ + override public function updateBuffer(clearBefore:Boolean = false):void + { + _field.setTextFormat(_form); + _field.width = _width = _field.textWidth + 4; + _field.height = _height = _field.textHeight + 4; + _source.fillRect(_sourceRect, 0); + _source.draw(_field); + super.updateBuffer(clearBefore); + } + + /** @private Centers the Text's originX/Y to its center. */ + override public function centerOrigin():void + { + originX = _width / 2; + originY = _height / 2; + } + + /** + * Text string. + */ + public function get text():String { return _text; } + public function set text(value:String):void + { + if (_text == value) return; + _field.text = _text = value; + updateBuffer(); + } + + /** + * Font family. + */ + public function get font():String { return _font; } + public function set font(value:String):void + { + if (_font == value) return; + _form.font = _font = value; + updateBuffer(); + } + + /** + * Font size. + */ + public function get size():uint { return _size; } + public function set size(value:uint):void + { + if (_size == value) return; + _form.size = _size = value; + updateBuffer(); + } + + /** + * Width of the text image. + */ + override public function get width():uint { return _width; } + + /** + * Height of the text image. + */ + override public function get height():uint { return _height; } + + // Text information. + /** @private */ private var _field:TextField = new TextField; + /** @private */ private var _width:uint; + /** @private */ private var _height:uint; + /** @private */ private var _form:TextFormat; + /** @private */ private var _text:String; + /** @private */ private var _font:String; + /** @private */ private var _size:uint; + + // Default font family. + // Use this option when compiling with Flex SDK 3 or lower + // [Embed(source = '04B_03__.TTF', fontFamily = 'default')] + // Use this option when compiling with Flex SDK 4 + [Embed(source = '04B_03__.TTF', embedAsCFF="false", fontFamily = 'default')] + /** @private */ private static var _FONT_DEFAULT:Class; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/TiledImage.as b/src/net/flashpunk/graphics/TiledImage.as new file mode 100644 index 0000000..0560ce3 --- /dev/null +++ b/src/net/flashpunk/graphics/TiledImage.as @@ -0,0 +1,102 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.display.Graphics; + import flash.geom.Rectangle; + import net.flashpunk.FP; + + /** + * Special Image object that can display blocks of tiles. + */ + public class TiledImage extends Image + { + /** + * Constructs the TiledImage. + * @param texture Source texture. + * @param width The width of the image (the texture will be drawn to fill this area). + * @param height The height of the image (the texture will be drawn to fill this area). + * @param clipRect An optional area of the source texture to use (eg. a tile from a tileset). + */ + public function TiledImage(texture:*, width:uint = 0, height:uint = 0, clipRect:Rectangle = null) + { + _width = width; + _height = height; + super(texture, clipRect); + } + + /** @private Creates the buffer. */ + override protected function createBuffer():void + { + if (!_width) _width = _sourceRect.width; + if (!_height) _height = _sourceRect.height; + _buffer = new BitmapData(_width, _height, true, 0); + _bufferRect = _buffer.rect; + } + + /** @private Updates the buffer. */ + override public function updateBuffer(clearBefore:Boolean = false):void + { + if (!_source) return; + if (!_texture) + { + _texture = new BitmapData(_sourceRect.width, _sourceRect.height, true, 0); + _texture.copyPixels(_source, _sourceRect, FP.zero); + } + _buffer.fillRect(_bufferRect, 0); + _graphics.clear(); + if (_offsetX != 0 || _offsetY != 0) + { + FP.matrix.identity(); + FP.matrix.tx = Math.round(_offsetX); + FP.matrix.ty = Math.round(_offsetY); + _graphics.beginBitmapFill(_texture, FP.matrix); + } + else _graphics.beginBitmapFill(_texture); + _graphics.drawRect(0, 0, _width, _height); + _buffer.draw(FP.sprite, null, _tint); + } + + /** + * The x-offset of the texture. + */ + public function get offsetX():Number { return _offsetX; } + public function set offsetX(value:Number):void + { + if (_offsetX == value) return; + _offsetX = value; + updateBuffer(); + } + + /** + * The y-offset of the texture. + */ + public function get offsetY():Number { return _offsetY; } + public function set offsetY(value:Number):void + { + if (_offsetY == value) return; + _offsetY = value; + updateBuffer(); + } + + /** + * Sets the texture offset. + * @param x The x-offset. + * @param y The y-offset. + */ + public function setOffset(x:Number, y:Number):void + { + if (_offsetX == x && _offsetY == y) return; + _offsetX = x; + _offsetY = y; + updateBuffer(); + } + + // Drawing information. + /** @private */ private var _graphics:Graphics = FP.sprite.graphics; + /** @private */ private var _texture:BitmapData; + /** @private */ private var _width:uint; + /** @private */ private var _height:uint; + /** @private */ private var _offsetX:Number = 0; + /** @private */ private var _offsetY:Number = 0; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/TiledSpritemap.as b/src/net/flashpunk/graphics/TiledSpritemap.as new file mode 100644 index 0000000..1950e7f --- /dev/null +++ b/src/net/flashpunk/graphics/TiledSpritemap.as @@ -0,0 +1,109 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.display.Graphics; + import net.flashpunk.FP; + + /** + * Special Spritemap object that can display blocks of animated sprites. + */ + public class TiledSpritemap extends Spritemap + { + /** + * Constructs the tiled spritemap. + * @param source Source image. + * @param frameWidth Frame width. + * @param frameHeight Frame height. + * @param width Width of the block to render. + * @param height Height of the block to render. + * @param callback Optional callback function for animation end. + */ + public function TiledSpritemap(source:*, frameWidth:uint = 0, frameHeight:uint = 0, width:uint = 0, height:uint = 0, callback:Function = null) + { + _imageWidth = width; + _imageHeight = height; + super(source, frameWidth, frameHeight, callback); + } + + /** @private Creates the buffer. */ + override protected function createBuffer():void + { + if (!_imageWidth) _imageWidth = _sourceRect.width; + if (!_imageHeight) _imageHeight = _sourceRect.height; + _buffer = new BitmapData(_imageWidth, _imageHeight, true, 0); + _bufferRect = _buffer.rect; + } + + /** @private Updates the buffer. */ + override public function updateBuffer(clearBefore:Boolean = false):void + { + // get position of the current frame + _rect.x = _rect.width * _frame; + _rect.y = uint(_rect.x / _width) * _rect.height; + _rect.x %= _width; + if (_flipped) _rect.x = (_width - _rect.width) - _rect.x; + + // render it repeated to the buffer + var xx:int = int(_offsetX) % _imageWidth, + yy:int = int(_offsetY) % _imageHeight; + if (xx >= 0) xx -= _imageWidth; + if (yy >= 0) yy -= _imageHeight; + FP.point.x = xx; + FP.point.y = yy; + while (FP.point.y < _imageHeight) + { + while (FP.point.x < _imageWidth) + { + _buffer.copyPixels(_source, _sourceRect, FP.point); + FP.point.x += _sourceRect.width; + } + FP.point.x = xx; + FP.point.y += _sourceRect.height; + } + + // tint the buffer + if (_tint) _buffer.colorTransform(_bufferRect, _tint); + } + + /** + * The x-offset of the texture. + */ + public function get offsetX():Number { return _offsetX; } + public function set offsetX(value:Number):void + { + if (_offsetX == value) return; + _offsetX = value; + updateBuffer(); + } + + /** + * The y-offset of the texture. + */ + public function get offsetY():Number { return _offsetY; } + public function set offsetY(value:Number):void + { + if (_offsetY == value) return; + _offsetY = value; + updateBuffer(); + } + + /** + * Sets the texture offset. + * @param x The x-offset. + * @param y The y-offset. + */ + public function setOffset(x:Number, y:Number):void + { + if (_offsetX == x && _offsetY == y) return; + _offsetX = x; + _offsetY = y; + updateBuffer(); + } + + /** @private */ private var _graphics:Graphics = FP.sprite.graphics; + /** @private */ private var _imageWidth:uint; + /** @private */ private var _imageHeight:uint; + /** @private */ private var _offsetX:Number = 0; + /** @private */ private var _offsetY:Number = 0; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/graphics/Tilemap.as b/src/net/flashpunk/graphics/Tilemap.as new file mode 100644 index 0000000..2cac146 --- /dev/null +++ b/src/net/flashpunk/graphics/Tilemap.as @@ -0,0 +1,360 @@ +package net.flashpunk.graphics +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.Graphic; + import net.flashpunk.FP; + import net.flashpunk.masks.Grid; + + /** + * A canvas to which Tiles can be drawn for fast multiple tile rendering. + */ + public class Tilemap extends Canvas + { + /** + * If x/y positions should be used instead of columns/rows. + */ + public var usePositions:Boolean; + + /** + * Constructor. + * @param tileset The source tileset image. + * @param width Width of the tilemap, in pixels. + * @param height Height of the tilemap, in pixels. + * @param tileWidth Tile width. + * @param tileHeight Tile height. + */ + public function Tilemap(tileset:*, width:uint, height:uint, tileWidth:uint, tileHeight:uint) + { + // set some tilemap information + _width = width - (width % tileWidth); + _height = height - (height % tileHeight); + _columns = _width / tileWidth; + _rows = _height / tileHeight; + _map = new BitmapData(_columns, _rows, false, 0); + _temp = _map.clone(); + _tile = new Rectangle(0, 0, tileWidth, tileHeight); + + // create the canvas + _maxWidth -= _maxWidth % tileWidth; + _maxHeight -= _maxHeight % tileHeight; + super(_width, _height); + + // load the tileset graphic + if (tileset is Class) _set = FP.getBitmap(tileset); + else if (tileset is BitmapData) _set = tileset; + if (!_set) throw new Error("Invalid tileset graphic provided."); + _setColumns = uint(_set.width / tileWidth); + _setRows = uint(_set.height / tileHeight); + _setCount = _setColumns * _setRows; + } + + /** + * Sets the index of the tile at the position. + * @param column Tile column. + * @param row Tile row. + * @param index Tile index. + */ + public function setTile(column:uint, row:uint, index:uint = 0):void + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + } + index %= _setCount; + column %= _columns; + row %= _rows; + _tile.x = (index % _setColumns) * _tile.width; + _tile.y = uint(index / _setColumns) * _tile.height; + _map.setPixel(column, row, index); + draw(column * _tile.width, row * _tile.height, _set, _tile); + } + + /** + * Clears the tile at the position. + * @param column Tile column. + * @param row Tile row. + */ + public function clearTile(column:uint, row:uint):void + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + } + column %= _columns; + row %= _rows; + _tile.x = column * _tile.width; + _tile.y = row * _tile.height; + fill(_tile, 0, 0); + } + + /** + * Gets the tile index at the position. + * @param column Tile column. + * @param row Tile row. + * @return The tile index. + */ + public function getTile(column:uint, row:uint):uint + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + } + return _map.getPixel(column % _columns, row % _rows); + } + + /** + * Sets a rectangular region of tiles to the index. + * @param column First tile column. + * @param row First tile row. + * @param width Width in tiles. + * @param height Height in tiles. + * @param index Tile index. + */ + public function setRect(column:uint, row:uint, width:uint = 1, height:uint = 1, index:uint = 0):void + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + width /= _tile.width; + height /= _tile.height; + } + column %= _columns; + row %= _rows; + var c:uint = column, + r:uint = column + width, + b:uint = row + height, + u:Boolean = usePositions; + usePositions = false; + while (row < b) + { + while (column < r) + { + setTile(column, row, index); + column ++; + } + column = c; + row ++; + } + usePositions = u; + } + + /** + * Clears the rectangular region of tiles. + * @param column First tile column. + * @param row First tile row. + * @param width Width in tiles. + * @param height Height in tiles. + */ + public function clearRect(column:uint, row:uint, width:uint = 1, height:uint = 1):void + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + width /= _tile.width; + height /= _tile.height; + } + column %= _columns; + row %= _rows; + var c:uint = column, + r:uint = column + width, + b:uint = row + height, + u:Boolean = usePositions; + usePositions = false; + while (row < b) + { + while (column < r) + { + clearTile(column, row); + column ++; + } + column = c; + row ++; + } + usePositions = u; + } + + /** + * Loads the Tilemap tile index data from a string. + * @param str The string data, which is a set of tile values separated by the columnSep and rowSep strings. + * @param columnSep The string that separates each tile value on a row, default is ",". + * @param rowSep The string that separates each row of tiles, default is "\n". + */ + public function loadFromString(str:String, columnSep:String = ",", rowSep:String = "\n"):void + { + var row:Array = str.split(rowSep), + rows:int = row.length, + col:Array, cols:int, x:int, y:int; + for (y = 0; y < rows; y ++) + { + if (row[y] == '') continue; + col = row[y].split(columnSep), + cols = col.length; + for (x = 0; x < cols; x ++) + { + if (col[x] == '') continue; + setTile(x, y, uint(col[x])); + } + } + } + + /** + * Saves the Tilemap tile index data to a string. + * @param columnSep The string that separates each tile value on a row, default is ",". + * @param rowSep The string that separates each row of tiles, default is "\n". + */ + public function saveToString(columnSep:String = ",", rowSep:String = "\n"): String + { + var s:String = '', + x:int, y:int; + for (y = 0; y < _rows; y ++) + { + for (x = 0; x < _columns; x ++) + { + s += String(getTile(x, y)); + if (x != _columns - 1) s += columnSep; + } + if (y != _rows - 1) s += rowSep; + } + return s; + } + + /** + * Gets the index of a tile, based on its column and row in the tileset. + * @param tilesColumn Tileset column. + * @param tilesRow Tileset row. + * @return Index of the tile. + */ + public function getIndex(tilesColumn:uint, tilesRow:uint):uint + { + return (tilesRow % _setRows) * _setColumns + (tilesColumn % _setColumns); + } + + /** + * Shifts all the tiles in the tilemap. + * @param columns Horizontal shift. + * @param rows Vertical shift. + * @param wrap If tiles shifted off the canvas should wrap around to the other side. + */ + public function shiftTiles(columns:int, rows:int, wrap:Boolean = false):void + { + if (usePositions) + { + columns /= _tile.width; + rows /= _tile.height; + } + + if (!wrap) _temp.fillRect(_temp.rect, 0); + + if (columns != 0) + { + shift(columns * _tile.width, 0); + if (wrap) _temp.copyPixels(_map, _map.rect, FP.zero); + _map.scroll(columns, 0); + _point.y = 0; + _point.x = columns > 0 ? columns - _columns : columns + _columns; + _map.copyPixels(_temp, _temp.rect, _point); + + _rect.x = columns > 0 ? 0 : _columns + columns; + _rect.y = 0; + _rect.width = Math.abs(columns); + _rect.height = _rows; + updateRect(_rect, !wrap); + } + + if (rows != 0) + { + shift(0, rows * _tile.height); + if (wrap) _temp.copyPixels(_map, _map.rect, FP.zero); + _map.scroll(0, rows); + _point.x = 0; + _point.y = rows > 0 ? rows - _rows : rows + _rows; + _map.copyPixels(_temp, _temp.rect, _point); + + _rect.x = 0; + _rect.y = rows > 0 ? 0 : _rows + rows; + _rect.width = _columns; + _rect.height = Math.abs(rows); + updateRect(_rect, !wrap); + } + } + + /** @private Used by shiftTiles to update a rectangle of tiles from the tilemap. */ + private function updateRect(rect:Rectangle, clear:Boolean):void + { + var x:int = rect.x, + y:int = rect.y, + w:int = x + rect.width, + h:int = y + rect.height, + u:Boolean = usePositions; + usePositions = false; + if (clear) + { + while (y < h) + { + while (x < w) clearTile(x ++, y); + x = rect.x; + y ++; + } + } + else + { + while (y < h) + { + while (x < w) updateTile(x ++, y); + x = rect.x; + y ++; + } + } + usePositions = u; + } + + /** @private Used by shiftTiles to update a tile from the tilemap. */ + private function updateTile(column:uint, row:uint):void + { + setTile(column, row, _map.getPixel(column % _columns, row % _rows)); + } + + /** + * The tile width. + */ + public function get tileWidth():uint { return _tile.width; } + + /** + * The tile height. + */ + public function get tileHeight():uint { return _tile.height; } + + /** + * How many columns the tilemap has. + */ + public function get columns():uint { return _columns; } + + /** + * How many rows the tilemap has. + */ + public function get rows():uint { return _rows; } + + // Tilemap information. + /** @private */ private var _map:BitmapData; + /** @private */ private var _temp:BitmapData; + /** @private */ private var _columns:uint; + /** @private */ private var _rows:uint; + + // Tileset information. + /** @private */ private var _set:BitmapData; + /** @private */ private var _setColumns:uint; + /** @private */ private var _setRows:uint; + /** @private */ private var _setCount:uint; + /** @private */ private var _tile:Rectangle; + + // Global objects. + /** @private */ private var _rect:Rectangle = FP.rect; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/masks/Grid.as b/src/net/flashpunk/masks/Grid.as new file mode 100644 index 0000000..71a5069 --- /dev/null +++ b/src/net/flashpunk/masks/Grid.as @@ -0,0 +1,268 @@ +package net.flashpunk.masks +{ + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.*; + + /** + * Uses a hash grid to determine collision, faster than + * using hundreds of Entities for tiled levels, etc. + */ + public class Grid extends Hitbox + { + /** + * If x/y positions should be used instead of columns/rows. + */ + public var usePositions:Boolean; + + /** + * Constructor. + * @param width Width of the grid, in pixels. + * @param height Height of the grid, in pixels. + * @param tileWidth Width of a grid tile, in pixels. + * @param tileHeight Height of a grid tile, in pixels. + * @param x X offset of the grid. + * @param y Y offset of the grid. + */ + public function Grid(width:uint, height:uint, tileWidth:uint, tileHeight:uint, x:int = 0, y:int = 0) + { + // check for illegal grid size + if (!width || !height || !tileWidth || !tileHeight) throw new Error("Illegal Grid, sizes cannot be 0."); + + // set grid properties + _columns = width / tileWidth; + _rows = height / tileHeight; + _data = new BitmapData(_columns, _rows, true, 0); + _tile = new Rectangle(0, 0, tileWidth, tileHeight); + _x = x; + _y = y; + _width = width; + _height = height; + + // set callback functions + _check[Mask] = collideMask; + _check[Hitbox] = collideHitbox; + _check[Pixelmask] = collidePixelmask; + } + + /** + * Sets the value of the tile. + * @param column Tile column. + * @param row Tile row. + * @param solid If the tile should be solid. + */ + public function setTile(column:uint = 0, row:uint = 0, solid:Boolean = true):void + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + } + _data.setPixel32(column, row, solid ? 0xFFFFFFFF : 0); + } + + /** + * Makes the tile non-solid. + * @param column Tile column. + * @param row Tile row. + */ + public function clearTile(column:uint = 0, row:uint = 0):void + { + setTile(column, row, false); + } + + /** + * Gets the value of a tile. + * @param column Tile column. + * @param row Tile row. + * @return tile value. + */ + public function getTile(column:uint = 0, row:uint = 0):Boolean + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + } + return _data.getPixel32(column, row) > 0; + } + + /** + * Sets the value of a rectangle region of tiles. + * @param column First column. + * @param row First row. + * @param width Columns to fill. + * @param height Rows to fill. + * @param fill Value to fill. + */ + public function setRect(column:uint = 0, row:uint = 0, width:int = 1, height:int = 1, solid:Boolean = true):void + { + if (usePositions) + { + column /= _tile.width; + row /= _tile.height; + width /= _tile.width; + height /= _tile.height; + } + _rect.x = column; + _rect.y = row; + _rect.width = width; + _rect.height = height; + _data.fillRect(_rect, solid ? 0xFFFFFFFF : 0); + } + + /** + * Makes the rectangular region of tiles non-solid. + * @param column First column. + * @param row First row. + * @param width Columns to fill. + * @param height Rows to fill. + */ + public function clearRect(column:uint = 0, row:uint = 0, width:int = 1, height:int = 1):void + { + setRect(column, row, width, height, false); + } + + /** + * Loads the grid data from a string. + * @param str The string data, which is a set of tile values (0 or 1) separated by the columnSep and rowSep strings. + * @param columnSep The string that separates each tile value on a row, default is ",". + * @param rowSep The string that separates each row of tiles, default is "\n". + */ + public function loadFromString(str:String, columnSep:String = ",", rowSep:String = "\n"):void + { + var row:Array = str.split(rowSep), + rows:int = row.length, + col:Array, cols:int, x:int, y:int; + for (y = 0; y < rows; y ++) + { + if (row[y] == '') continue; + col = row[y].split(columnSep), + cols = col.length; + for (x = 0; x < cols; x ++) + { + if (col[x] == '') continue; + setTile(x, y, uint(col[x]) > 0); + } + } + } + + /** + * Saves the grid data to a string. + * @param columnSep The string that separates each tile value on a row, default is ",". + * @param rowSep The string that separates each row of tiles, default is "\n". + */ + public function saveToString(columnSep:String = ",", rowSep:String = "\n"): String + { + var s:String = '', + x:int, y:int; + for (y = 0; y < _rows; y ++) + { + for (x = 0; x < _columns; x ++) + { + s += String(getTile(x, y)); + if (x != _columns - 1) s += columnSep; + } + if (y != _rows - 1) s += rowSep; + } + return s; + } + + /** + * The tile width. + */ + public function get tileWidth():uint { return _tile.width; } + + /** + * The tile height. + */ + public function get tileHeight():uint { return _tile.height; } + + /** + * How many columns the grid has + */ + public function get columns():uint { return _columns; } + + /** + * How many rows the grid has. + */ + public function get rows():uint { return _rows; } + + /** + * The grid data. + */ + public function get data():BitmapData { return _data; } + + /** @private Collides against an Entity. */ + private function collideMask(other:Mask):Boolean + { + _rect.x = other.parent.x - other.parent.originX - parent.x + parent.originX; + _rect.y = other.parent.y - other.parent.originY - parent.y + parent.originY; + _point.x = int((_rect.x + other.parent.width - 1) / _tile.width) + 1; + _point.y = int((_rect.y + other.parent.height -1) / _tile.height) + 1; + _rect.x = int(_rect.x / _tile.width); + _rect.y = int(_rect.y / _tile.height); + _rect.width = _point.x - _rect.x; + _rect.height = _point.y - _rect.y; + return _data.hitTest(FP.zero, 1, _rect); + } + + /** @private Collides against a Hitbox. */ + private function collideHitbox(other:Hitbox):Boolean + { + _rect.x = other.parent.x + other._x - parent.x - _x; + _rect.y = other.parent.y + other._y - parent.y - _y; + _point.x = int((_rect.x + other._width - 1) / _tile.width) + 1; + _point.y = int((_rect.y + other._height -1) / _tile.height) + 1; + _rect.x = int(_rect.x / _tile.width); + _rect.y = int(_rect.y / _tile.height); + _rect.width = _point.x - _rect.x; + _rect.height = _point.y - _rect.y; + return _data.hitTest(FP.zero, 1, _rect); + } + + /** @private Collides against a Pixelmask. */ + private function collidePixelmask(other:Pixelmask):Boolean + { + var x1:int = other.parent.x + other._x - parent.x - _x, + y1:int = other.parent.y + other._y - parent.y - _y, + x2:int = ((x1 + other._width - 1) / _tile.width), + y2:int = ((y1 + other._height - 1) / _tile.height); + _point.x = x1; + _point.y = y1; + x1 /= _tile.width; + y1 /= _tile.height; + _tile.x = x1 * _tile.width; + _tile.y = y1 * _tile.height; + var xx:int = x1; + while (y1 <= y2) + { + while (x1 <= x2) + { + if (_data.getPixel32(x1, y1)) + { + if (other._data.hitTest(_point, 1, _tile)) return true; + } + x1 ++; + _tile.x += _tile.width; + } + x1 = xx; + y1 ++; + _tile.x = x1 * _tile.width; + _tile.y += _tile.height; + } + return false; + } + + // Grid information. + /** @private */ private var _data:BitmapData; + /** @private */ private var _columns:uint; + /** @private */ private var _rows:uint; + /** @private */ private var _tile:Rectangle; + /** @private */ private var _rect:Rectangle = FP.rect; + /** @private */ private var _point:Point = FP.point; + /** @private */ private var _point2:Point = FP.point2; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/masks/Hitbox.as b/src/net/flashpunk/masks/Hitbox.as new file mode 100644 index 0000000..41982e6 --- /dev/null +++ b/src/net/flashpunk/masks/Hitbox.as @@ -0,0 +1,114 @@ +package net.flashpunk.masks +{ + import net.flashpunk.*; + + /** + * Uses parent's hitbox to determine collision. This class is used + * internally by FlashPunk, you don't need to use this class because + * this is the default behaviour of Entities without a Mask object. + */ + public class Hitbox extends Mask + { + /** + * Constructor. + * @param width Width of the hitbox. + * @param height Height of the hitbox. + * @param x X offset of the hitbox. + * @param y Y offset of the hitbox. + */ + public function Hitbox(width:uint = 1, height:uint = 1, x:int = 0, y:int = 0) + { + _width = width; + _height = height; + _x = x; + _y = y; + _check[Mask] = collideMask; + _check[Hitbox] = collideHitbox; + } + + /** @private Collides against an Entity. */ + private function collideMask(other:Mask):Boolean + { + return parent.x + _x + _width > other.parent.x - other.parent.originX + && parent.y + _y + _height > other.parent.y - other.parent.originY + && parent.x + _x < other.parent.x - other.parent.originX + other.parent.width + && parent.y + _y < other.parent.y - other.parent.originY + other.parent.height; + } + + /** @private Collides against a Hitbox. */ + private function collideHitbox(other:Hitbox):Boolean + { + return parent.x + _x + _width > other.parent.x + other._x + && parent.y + _y + _height > other.parent.y + other._y + && parent.x + _x < other.parent.x + other._x + other._width + && parent.y + _y < other.parent.y + other._y + other._height; + } + + /** + * X offset. + */ + public function get x():int { return _x; } + public function set x(value:int):void + { + if (_x == value) return; + _x = value; + if (list) list.update(); + else if (parent) update(); + } + + /** + * Y offset. + */ + public function get y():int { return _y; } + public function set y(value:int):void + { + if (_y == value) return; + _y = value; + if (list) list.update(); + else if (parent) update(); + } + + /** + * Width. + */ + public function get width():int { return _width; } + public function set width(value:int):void + { + if (_width == value) return; + _width = value; + if (list) list.update(); + else if (parent) update(); + } + + /** + * Height. + */ + public function get height():int { return _height; } + public function set height(value:int):void + { + if (_height == value) return; + _height = value; + if (list) list.update(); + else if (parent) update(); + } + + /** @private Updates the parent's bounds for this mask. */ + override protected function update():void + { + // update entity bounds + parent.originX = -_x; + parent.originY = -_y; + parent.width = _width; + parent.height = _height; + + // update parent list + if (list) list.update(); + } + + // Hitbox information. + /** @private */ internal var _width:uint; + /** @private */ internal var _height:uint; + /** @private */ internal var _x:int; + /** @private */ internal var _y:int; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/masks/Masklist.as b/src/net/flashpunk/masks/Masklist.as new file mode 100644 index 0000000..ce38ebe --- /dev/null +++ b/src/net/flashpunk/masks/Masklist.as @@ -0,0 +1,159 @@ +package net.flashpunk.masks +{ + import net.flashpunk.*; + import net.flashpunk.masks.Masklist; + + /** + * A Mask that can contain multiple Masks of one or various types. + */ + public class Masklist extends Hitbox + { + /** + * Constructor. + * @param ...mask Masks to add to the list. + */ + public function Masklist(...mask) + { + for each (var m:Mask in mask) add(m); + } + + /** @private Collide against a mask. */ + override public function collide(mask:Mask):Boolean + { + for each (var m:Mask in _masks) + { + if (m.collide(mask)) return true; + } + return false; + } + + /** @private Collide against a Masklist. */ + override protected function collideMasklist(other:Masklist):Boolean + { + for each (var a:Mask in _masks) + { + for each (var b:Mask in other._masks) + { + if (a.collide(b)) return true; + } + } + return true; + } + + /** + * Adds a Mask to the list. + * @param mask The Mask to add. + * @return The added Mask. + */ + public function add(mask:Mask):Mask + { + _masks[_count ++] = mask; + mask.list = this; + update(); + return mask; + } + + /** + * Removes the Mask from the list. + * @param mask The Mask to remove. + * @return The removed Mask. + */ + public function remove(mask:Mask):Mask + { + if (_masks.indexOf(mask) < 0) return mask; + _temp.length = 0; + for each (var m:Mask in _masks) + { + if (m == mask) + { + mask.list = null; + _count --; + update(); + } + else _temp[_temp.length] = m; + } + var temp:Vector. = _masks; + _masks = _temp; + _temp = temp; + return mask; + } + + /** + * Removes the Mask at the index. + * @param index The Mask index. + */ + public function removeAt(index:uint = 0):void + { + _temp.length = 0; + var i:int = _masks.length; + index %= i; + while (i --) + { + if (i == index) + { + _masks[index].list = null; + _count --; + update(); + } + else _temp[_temp.length] = _masks[index]; + } + var temp:Vector. = _masks; + _masks = _temp; + _temp = temp; + } + + /** + * Removes all Masks from the list. + */ + public function removeAll():void + { + for each (var m:Mask in _masks) m.list = null; + _masks.length = _temp.length = _count = 0; + update(); + } + + /** + * Gets a Mask from the list. + * @param index The Mask index. + * @return The Mask at the index. + */ + public function getMask(index:uint = 0):Mask + { + return _masks[index % _masks.length]; + } + + /** @private Updates the parent's bounds for this mask. */ + override protected function update():void + { + // find bounds of the contained masks + var t:int, l:int, r:int, b:int, h:Hitbox, i:int = _count; + while (i --) + { + if ((h = _masks[i] as Hitbox)) + { + if (h._x < l) l = h._x; + if (h._y < t) t = h._y; + if (h._x + h._width > r) r = h._x + h._width; + if (h._y + h._height > b) b = h._y + h._height; + } + } + + // update hitbox bounds + _x = l; + _y = t; + _width = r - l; + _height = b - t; + super.update(); + } + + /** + * Amount of Masks in the list. + */ + public function get count():uint { return _count; } + + // List information. + /** @private */ private var _masks:Vector. = new Vector.; + /** @private */ private var _temp:Vector. = new Vector.; + /** @private */ private var _count:uint; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/masks/Pixelmask.as b/src/net/flashpunk/masks/Pixelmask.as new file mode 100644 index 0000000..a25a851 --- /dev/null +++ b/src/net/flashpunk/masks/Pixelmask.as @@ -0,0 +1,97 @@ +package net.flashpunk.masks +{ + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.*; + + /** + * A bitmap mask used for pixel-perfect collision. + */ + public class Pixelmask extends Hitbox + { + /** + * Alpha threshold of the bitmap used for collision. + */ + public var threshold:uint = 1; + + /** + * Constructor. + * @param source The image to use as a mask. + * @param x X offset of the mask. + * @param y Y offset of the mask. + */ + public function Pixelmask(source:*, x:int = 0, y:int = 0) + { + // fetch mask data + if (source is BitmapData) _data = source; + if (source is Class) _data = FP.getBitmap(source); + if (!_data) throw new Error("Invalid Pixelmask source image."); + + // set mask properties + _width = data.width; + _height = data.height; + _x = x; + _y = y; + + // set callback functions + _check[Mask] = collideMask; + _check[Pixelmask] = collidePixelmask; + _check[Hitbox] = collideHitbox; + } + + /** @private Collide against an Entity. */ + private function collideMask(other:Mask):Boolean + { + _point.x = parent.x + _x; + _point.y = parent.y + _y; + _rect.x = other.parent.x - other.parent.originX; + _rect.y = other.parent.y - other.parent.originY; + _rect.width = other.parent.width; + _rect.height = other.parent.height; + return _data.hitTest(_point, threshold, _rect); + } + + /** @private Collide against a Hitbox. */ + private function collideHitbox(other:Hitbox):Boolean + { + _point.x = parent.x + _x; + _point.y = parent.y + _y; + _rect.x = other.parent.x + other._x; + _rect.y = other.parent.y + other._y; + _rect.width = other._width; + _rect.height = other._height; + return _data.hitTest(_point, threshold, _rect); + } + + /** @private Collide against a Pixelmask. */ + private function collidePixelmask(other:Pixelmask):Boolean + { + _point.x = parent.x + _x; + _point.y = parent.y + _y; + _point2.x = other.parent.x + other._x; + _point2.y = other.parent.y + other._y; + return _data.hitTest(_point, threshold, other._data, _point2, other.threshold); + } + + /** + * Current BitmapData mask. + */ + public function get data():BitmapData { return _data; } + public function set data(value:BitmapData):void + { + _data = value; + _width = value.width; + _height = value.height; + update(); + } + + // Pixelmask information. + /** @private */ internal var _data:BitmapData; + + // Global objects. + /** @private */ private var _rect:Rectangle = FP.rect; + /** @private */ private var _point:Point = FP.point; + /** @private */ private var _point2:Point = FP.point2; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/misc/Alarm.as b/src/net/flashpunk/tweens/misc/Alarm.as new file mode 100644 index 0000000..dc7adf2 --- /dev/null +++ b/src/net/flashpunk/tweens/misc/Alarm.as @@ -0,0 +1,46 @@ +package net.flashpunk.tweens.misc +{ + import net.flashpunk.Tween; + + /** + * A simple alarm, useful for timed events, etc. + */ + public class Alarm extends Tween + { + /** + * Constructor. + * @param duration Duration of the alarm. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function Alarm(duration:Number, complete:Function = null, type:uint = 0) + { + super(duration, type, complete, null); + } + + /** + * Sets the alarm. + * @param duration Duration of the alarm. + */ + public function reset(duration:Number):void + { + _target = duration; + start(); + } + + /** + * How much time has passed since reset. + */ + public function get elapsed():Number { return _time; } + + /** + * Current alarm duration. + */ + public function get duration():Number { return _target; } + + /** + * Time remaining on the alarm. + */ + public function get remaining():Number { return _target - _time; } + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/misc/AngleTween.as b/src/net/flashpunk/tweens/misc/AngleTween.as new file mode 100644 index 0000000..11ef91e --- /dev/null +++ b/src/net/flashpunk/tweens/misc/AngleTween.as @@ -0,0 +1,58 @@ +package net.flashpunk.tweens.misc +{ + import net.flashpunk.FP; + import net.flashpunk.Tween; + + /** + * Tweens from one angle to another. + */ + public class AngleTween extends Tween + { + /** + * The current value. + */ + public var angle:Number = 0; + + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function AngleTween(complete:Function = null, type:uint = 0) + { + super(0, type, complete); + } + + /** + * Tweens the value from one angle to another. + * @param fromAngle Start angle. + * @param toAngle End angle. + * @param duration Duration of the tween. + * @param ease Optional easer function. + */ + public function tween(fromAngle:Number, toAngle:Number, duration:Number, ease:Function = null):void + { + _start = angle = fromAngle; + var d:Number = toAngle - angle, + a:Number = Math.abs(d); + if (a > 181) _range = (360 - a) * (d > 0 ? -1 : 1); + else if (a < 179) _range = d; + else _range = FP.choose(180, -180); + _target = duration; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + angle = (_start + _range * _t) % 360; + if (angle < 0) angle += 360; + } + + // Tween information. + /** @private */ private var _start:Number; + /** @private */ private var _range:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/misc/ColorTween.as b/src/net/flashpunk/tweens/misc/ColorTween.as new file mode 100644 index 0000000..0af04a5 --- /dev/null +++ b/src/net/flashpunk/tweens/misc/ColorTween.as @@ -0,0 +1,100 @@ +package net.flashpunk.tweens.misc +{ + import net.flashpunk.Tween; + + /** + * Tweens a color's red, green, and blue properties + * independently. Can also tween an alpha value. + */ + public class ColorTween extends Tween + { + /** + * The current color. + */ + public var color:uint; + + /** + * The current alpha. + */ + public var alpha:Number = 1; + + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function ColorTween(complete:Function = null, type:uint = 0) + { + super(0, type, complete); + } + + /** + * Tweens the color to a new color and an alpha to a new alpha. + * @param duration Duration of the tween. + * @param fromColor Start color. + * @param toColor End color. + * @param fromAlpha Start alpha + * @param toAlpha End alpha. + * @param ease Optional easer function. + */ + public function tween(duration:Number, fromColor:uint, toColor:uint, fromAlpha:Number = 1, toAlpha:Number = 1, ease:Function = null):void + { + fromColor &= 0xFFFFFF; + toColor &= 0xFFFFFF; + color = fromColor; + _r = fromColor >> 16 & 0xFF; + _g = fromColor >> 8 & 0xFF; + _b = fromColor & 0xFF; + _startR = _r / 255; + _startG = _g / 255; + _startB = _b / 255; + _rangeR = ((toColor >> 16 & 0xFF) / 255) - _startR; + _rangeG = ((toColor >> 8 & 0xFF) / 255) - _startG; + _rangeB = ((toColor & 0xFF) / 255) - _startB; + _startA = alpha = fromAlpha; + _rangeA = toAlpha - alpha; + _target = duration; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + alpha = _startA + _rangeA * _t; + _r = uint((_startR + _rangeR * _t) * 255); + _g = uint((_startG + _rangeG * _t) * 255); + _b = uint((_startB + _rangeB * _t) * 255); + color = _r << 16 | _g << 8 | _b; + } + + /** + * Red value of the current color, from 0 to 255. + */ + public function get red():uint { return _r; } + + /** + * Green value of the current color, from 0 to 255. + */ + public function get green():uint { return _g; } + + /** + * Blue value of the current color, from 0 to 255. + */ + public function get blue():uint { return _b; } + + // Color information. + /** @private */ private var _r:uint; + /** @private */ private var _g:uint; + /** @private */ private var _b:uint; + /** @private */ private var _startA:Number; + /** @private */ private var _startR:Number; + /** @private */ private var _startG:Number; + /** @private */ private var _startB:Number; + /** @private */ private var _rangeA:Number; + /** @private */ private var _rangeR:Number; + /** @private */ private var _rangeG:Number; + /** @private */ private var _rangeB:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/misc/MultiVarTween.as b/src/net/flashpunk/tweens/misc/MultiVarTween.as new file mode 100644 index 0000000..8aa532b --- /dev/null +++ b/src/net/flashpunk/tweens/misc/MultiVarTween.as @@ -0,0 +1,61 @@ +package net.flashpunk.tweens.misc +{ + import net.flashpunk.Tween; + + /** + * Tweens multiple numeric public properties of an Object simultaneously. + */ + public class MultiVarTween extends Tween + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function MultiVarTween(complete:Function = null, type:uint = 0) + { + super(0, type, complete); + } + + /** + * Tweens multiple numeric public properties. + * @param object The object containing the properties. + * @param values An object containing key/value pairs of properties and target values. + * @param duration Duration of the tween. + * @param ease Optional easer function. + */ + public function tween(object:Object, values:Object, duration:Number, ease:Function = null):void + { + _object = object; + _vars.length = 0; + _start.length = 0; + _range.length = 0; + _target = duration; + _ease = ease; + for (var p:String in values) + { + if (!object.hasOwnProperty(p)) throw new Error("The Object does not have the property\"" + p + "\", or it is not accessible."); + var a:* = _object[p] as Number; + if (a == null) throw new Error("The property \"" + p + "\" is not numeric."); + _vars.push(p); + _start.push(a); + _range.push(values[p] - a); + } + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + var i:int = _vars.length; + while (i --) _object[_vars[i]] = _start[i] + _range[i] * _t; + } + + // Tween information. + /** @private */ private var _object:Object; + /** @private */ private var _vars:Vector. = new Vector.; + /** @private */ private var _start:Vector. = new Vector.; + /** @private */ private var _range:Vector. = new Vector.; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/misc/NumTween.as b/src/net/flashpunk/tweens/misc/NumTween.as new file mode 100644 index 0000000..88a21d1 --- /dev/null +++ b/src/net/flashpunk/tweens/misc/NumTween.as @@ -0,0 +1,52 @@ +package net.flashpunk.tweens.misc +{ + import net.flashpunk.Tween; + + /** + * Tweens a numeric value. + */ + public class NumTween extends Tween + { + /** + * The current value. + */ + public var value:Number = 0; + + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function NumTween(complete:Function = null, type:uint = 0) + { + super(0, type, complete); + } + + /** + * Tweens the value from one value to another. + * @param fromValue Start value. + * @param toValue End value. + * @param duration Duration of the tween. + * @param ease Optional easer function. + */ + public function tween(fromValue:Number, toValue:Number, duration:Number, ease:Function = null):void + { + _start = value = fromValue; + _range = toValue - value; + _target = duration; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + value = _start + _range * _t; + } + + // Tween information. + /** @private */ private var _start:Number; + /** @private */ private var _range:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/misc/VarTween.as b/src/net/flashpunk/tweens/misc/VarTween.as new file mode 100644 index 0000000..841d8e1 --- /dev/null +++ b/src/net/flashpunk/tweens/misc/VarTween.as @@ -0,0 +1,55 @@ +package net.flashpunk.tweens.misc +{ + import net.flashpunk.Tween; + + /** + * Tweens a numeric public property of an Object. + */ + public class VarTween extends Tween + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function VarTween(complete:Function = null, type:uint = 0) + { + super(0, type, complete); + } + + /** + * Tweens a numeric public property. + * @param object The object containing the property. + * @param property The name of the property (eg. "x"). + * @param to Value to tween to. + * @param duration Duration of the tween. + * @param ease Optional easer function. + */ + public function tween(object:Object, property:String, to:Number, duration:Number, ease:Function = null):void + { + _object = object; + _property = property; + _ease = ease; + if (!object.hasOwnProperty(property)) throw new Error("The Object does not have the property\"" + property + "\", or it is not accessible."); + var a:* = _object[property] as Number; + if (a == null) throw new Error("The property \"" + property + "\" is not numeric."); + _start = _object[property]; + _range = to - _start; + _target = duration; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + _object[_property] = _start + _range * _t; + } + + // Tween information. + /** @private */ private var _object:Object; + /** @private */ private var _property:String; + /** @private */ private var _start:Number; + /** @private */ private var _range:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/CircularMotion.as b/src/net/flashpunk/tweens/motion/CircularMotion.as new file mode 100644 index 0000000..efa5cde --- /dev/null +++ b/src/net/flashpunk/tweens/motion/CircularMotion.as @@ -0,0 +1,94 @@ +package net.flashpunk.tweens.motion +{ + import flash.geom.Point; + import net.flashpunk.FP; + import net.flashpunk.utils.Ease; + + /** + * Determines a circular motion. + */ + public class CircularMotion extends Motion + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function CircularMotion(complete:Function = null, type:uint = 0) + { + super(0, complete, type, null); + } + + /** + * Starts moving along a circle. + * @param centerX X position of the circle's center. + * @param centerY Y position of the circle's center. + * @param radius Radius of the circle. + * @param angle Starting position on the circle. + * @param clockwise If the motion is clockwise. + * @param duration Duration of the movement. + * @param ease Optional easer function. + */ + public function setMotion(centerX:Number, centerY:Number, radius:Number, angle:Number, clockwise:Boolean, duration:Number, ease:Function = null):void + { + _centerX = centerX; + _centerY = centerY; + _radius = radius; + _angle = _angleStart = angle * FP.RAD; + _angleFinish = _CIRC * (clockwise ? 1 : -1); + _target = duration; + _ease = ease; + start(); + } + + /** + * Starts moving along a circle at the speed. + * @param centerX X position of the circle's center. + * @param centerY Y position of the circle's center. + * @param radius Radius of the circle. + * @param angle Starting position on the circle. + * @param clockwise If the motion is clockwise. + * @param speed Speed of the movement. + * @param ease Optional easer function. + */ + public function setMotionSpeed(centerX:Number, centerY:Number, radius:Number, angle:Number, clockwise:Boolean, speed:Number, ease:Function = null):void + { + _centerX = centerX; + _centerY = centerY; + _radius = radius; + _angle = _angleStart = angle * FP.RAD; + _angleFinish = _CIRC * (clockwise ? 1 : -1); + _target = (_radius * _CIRC) / speed; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + _angle = _angleStart + _angleFinish * _t; + x = _centerX + Math.cos(_angle) * _radius; + y = _centerY + Math.sin(_angle) * _radius; + } + + /** + * The current position on the circle. + */ + public function get angle():Number { return _angle; } + + /** + * The circumference of the current circle motion. + */ + public function get circumference():Number { return _radius * _CIRC; } + + // Circle information. + /** @private */ private var _centerX:Number = 0; + /** @private */ private var _centerY:Number = 0; + /** @private */ private var _radius:Number = 0; + /** @private */ private var _angle:Number = 0; + /** @private */ private var _angleStart:Number = 0; + /** @private */ private var _angleFinish:Number = 0; + /** @private */ private static const _CIRC:Number = Math.PI * 2; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/CubicMotion.as b/src/net/flashpunk/tweens/motion/CubicMotion.as new file mode 100644 index 0000000..5162cd0 --- /dev/null +++ b/src/net/flashpunk/tweens/motion/CubicMotion.as @@ -0,0 +1,69 @@ +package net.flashpunk.tweens.motion +{ + import flash.geom.Point; + import net.flashpunk.utils.Ease; + + /** + * Determines motion along a cubic curve. + */ + public class CubicMotion extends Motion + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function CubicMotion(complete:Function = null, type:uint = 0) + { + super(0, complete, type, null); + } + + /** + * Starts moving along the curve. + * @param fromX X start. + * @param fromY Y start. + * @param aX First control x. + * @param aY First control y. + * @param bX Second control x. + * @param bY Second control y. + * @param toX X finish. + * @param toY Y finish. + * @param duration Duration of the movement. + * @param ease Optional easer function. + */ + public function setMotion(fromX:Number, fromY:Number, aX:Number, aY:Number, bX:Number, bY:Number, toX:Number, toY:Number, duration:Number, ease:Function = null):void + { + x = _fromX = fromX; + y = _fromY = fromY; + _aX = aX; + _aY = aY; + _bX = bX; + _bY = bY; + _toX = toX; + _toY = toY; + _target = duration; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + x = _t * _t * _t * (_toX + 3 * (_aX - _bX) - _fromX) + 3 * _t * _t * (_fromX - 2 * _aX + _bX) + 3 * _t * (_aX - _fromX) + _fromX; + y = _t * _t * _t * (_toY + 3 * (_aY - _bY) - _fromY) + 3 * _t * _t * (_fromY - 2 * _aY + _bY) + 3 * _t * (_aY - _fromY) + _fromY; + } + + // Curve information. + /** @private */ private var _fromX:Number = 0; + /** @private */ private var _fromY:Number = 0; + /** @private */ private var _toX:Number = 0; + /** @private */ private var _toY:Number = 0; + /** @private */ private var _aX:Number = 0; + /** @private */ private var _aY:Number = 0; + /** @private */ private var _bX:Number = 0; + /** @private */ private var _bY:Number = 0; + /** @private */ private var _ttt:Number; + /** @private */ private var _tt:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/LinearMotion.as b/src/net/flashpunk/tweens/motion/LinearMotion.as new file mode 100644 index 0000000..02623f0 --- /dev/null +++ b/src/net/flashpunk/tweens/motion/LinearMotion.as @@ -0,0 +1,86 @@ +package net.flashpunk.tweens.motion +{ + import flash.geom.Point; + + /** + * Determines motion along a line, from one point to another. + */ + public class LinearMotion extends Motion + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function LinearMotion(complete:Function = null, type:uint = 0) + { + super(0,complete, type, null); + } + + /** + * Starts moving along a line. + * @param fromX X start. + * @param fromY Y start. + * @param toX X finish. + * @param toY Y finish. + * @param duration Duration of the movement. + * @param ease Optional easer function. + */ + public function setMotion(fromX:Number, fromY:Number, toX:Number, toY:Number, duration:Number, ease:Function = null):void + { + _distance = -1; + x = _fromX = fromX; + y = _fromY = fromY; + _moveX = toX - fromX; + _moveY = toY - fromY; + _target = duration; + _ease = ease; + start(); + } + + /** + * Starts moving along a line at the speed. + * @param fromX X start. + * @param fromY Y start. + * @param toX X finish. + * @param toY Y finish. + * @param speed Speed of the movement. + * @param ease Optional easer function. + */ + public function setMotionSpeed(fromX:Number, fromY:Number, toX:Number, toY:Number, speed:Number, ease:Function = null):void + { + _distance = -1; + x = _fromX = fromX; + y = _fromY = fromY; + _moveX = toX - fromX; + _moveY = toY - fromY; + _target = distance / speed; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + x = _fromX + _moveX * _t; + y = _fromY + _moveY * _t; + } + + /** + * Length of the current line of movement. + */ + public function get distance():Number + { + if (_distance >= 0) return _distance; + return (_distance = Math.sqrt(_moveX * _moveX + _moveY * _moveY)); + } + + // Line information. + /** @private */ private var _fromX:Number = 0; + /** @private */ private var _fromY:Number = 0; + /** @private */ private var _moveX:Number = 0; + /** @private */ private var _moveY:Number = 0; + /** @private */ private var _distance:Number = - 1; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/LinearPath.as b/src/net/flashpunk/tweens/motion/LinearPath.as new file mode 100644 index 0000000..62eaf11 --- /dev/null +++ b/src/net/flashpunk/tweens/motion/LinearPath.as @@ -0,0 +1,133 @@ +package net.flashpunk.tweens.motion +{ + import flash.geom.Point; + import net.flashpunk.FP; + + /** + * Determines linear motion along a set of points. + */ + public class LinearPath extends Motion + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function LinearPath(complete:Function = null, type:uint = 0) + { + super(0, complete, type, null); + _pointD[0] = _pointT[0] = 0; + } + + /** + * Starts moving along the path. + * @param duration Duration of the movement. + * @param ease Optional easer function. + */ + public function setMotion(duration:Number, ease:Function = null):void + { + updatePath(); + _target = duration; + _speed = _distance / duration; + _ease = ease; + start(); + } + + /** + * Starts moving along the path at the speed. + * @param speed Speed of the movement. + * @param ease Optional easer function. + */ + public function setMotionSpeed(speed:Number, ease:Function = null):void + { + updatePath(); + _target = _distance / speed; + _speed = speed; + _ease = ease; + start(); + } + + /** + * Adds the point to the path. + * @param x X position. + * @param y Y position. + */ + public function addPoint(x:Number = 0, y:Number = 0):void + { + if (_last) + { + _distance += Math.sqrt((x - _last.x) * (x - _last.x) + (y - _last.y) * (y - _last.y)); + _pointD[_points.length] = _distance; + } + _points[_points.length] = _last = new Point(x, y); + } + + /** + * Gets a point on the path. + * @param index Index of the point. + * @return The Point object. + */ + public function getPoint(index:uint = 0):Point + { + if (!_points.length) throw new Error("No points have been added to the path yet."); + return _points[index % _points.length]; + } + + /** @private Starts the Tween. */ + override public function start():void + { + _index = 0; + super.start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + if (_index < _points.length - 1) + { + while (_t > _pointT[_index + 1]) _index ++; + } + var td:Number = _pointT[_index], + tt:Number = _pointT[_index + 1] - td; + td = (_t - td) / tt; + _prev = _points[_index]; + _next = _points[_index + 1]; + x = _prev.x + (_next.x - _prev.x) * td; + y = _prev.y + (_next.y - _prev.y) * td; + } + + /** @private Updates the path, preparing it for motion. */ + private function updatePath():void + { + if (_points.length < 2) throw new Error("A LinearPath must have at least 2 points to operate."); + if (_pointD.length == _pointT.length) return; + // evaluate t for each point + var i:int = 0; + while (i < _points.length) _pointT[i] = _pointD[i ++] / _distance; + } + + /** + * The full length of the path. + */ + public function get distance():Number { return _distance; } + + /** + * How many points are on the path. + */ + public function get pointCount():Number { return _points.length; } + + // Path information. + /** @private */ private var _points:Vector. = new Vector.; + /** @private */ private var _pointD:Vector. = new Vector.; + /** @private */ private var _pointT:Vector. = new Vector.; + /** @private */ private var _distance:Number = 0; + /** @private */ private var _speed:Number = 0; + /** @private */ private var _index:uint = 0; + + // Line information. + /** @private */ private var _last:Point; + /** @private */ private var _prev:Point; + /** @private */ private var _next:Point; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/Motion.as b/src/net/flashpunk/tweens/motion/Motion.as new file mode 100644 index 0000000..0675fa2 --- /dev/null +++ b/src/net/flashpunk/tweens/motion/Motion.as @@ -0,0 +1,32 @@ +package net.flashpunk.tweens.motion +{ + import net.flashpunk.Tween; + + /** + * Base class for motion Tweens. + */ + public class Motion extends Tween + { + /** + * Current x position of the Tween. + */ + public var x:Number = 0; + + /** + * Current y position of the Tween. + */ + public var y:Number = 0; + + /** + * Constructor. + * @param duration Duration of the Tween. + * @param complete Optional completion callback. + * @param type Tween type. + * @param ease Optional easer function. + */ + public function Motion(duration:Number, complete:Function = null, type:uint = 0, ease:Function = null) + { + super(duration, type, complete, ease); + } + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/QuadMotion.as b/src/net/flashpunk/tweens/motion/QuadMotion.as new file mode 100644 index 0000000..6729c3c --- /dev/null +++ b/src/net/flashpunk/tweens/motion/QuadMotion.as @@ -0,0 +1,112 @@ +package net.flashpunk.tweens.motion +{ + import flash.geom.Point; + import net.flashpunk.FP; + import net.flashpunk.utils.Ease; + + /** + * Determines motion along a quadratic curve. + */ + public class QuadMotion extends Motion + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function QuadMotion(complete:Function = null, type:uint = 0) + { + super(0, complete, type, null); + } + + /** + * Starts moving along the curve. + * @param fromX X start. + * @param fromY Y start. + * @param controlX X control, used to determine the curve. + * @param controlY Y control, used to determine the curve. + * @param toX X finish. + * @param toY Y finish. + * @param duration Duration of the movement. + * @param ease Optional easer function. + */ + public function setMotion(fromX:Number, fromY:Number, controlX:Number, controlY:Number, toX:Number, toY:Number, duration:Number, ease:Function = null):void + { + _distance = -1; + x = _fromX = fromX; + y = _fromY = fromY; + _controlX = controlX; + _controlY = controlY; + _toX = toX; + _toY = toY; + _target = duration; + _ease = ease; + start(); + } + + /** + * Starts moving along the curve at the speed. + * @param fromX X start. + * @param fromY Y start. + * @param controlX X control, used to determine the curve. + * @param controlY Y control, used to determine the curve. + * @param toX X finish. + * @param toY Y finish. + * @param speed Speed of the movement. + * @param ease Optional easer function. + */ + public function setMotionSpeed(fromX:Number, fromY:Number, controlX:Number, controlY:Number, toX:Number, toY:Number, speed:Number, ease:Function = null):void + { + _distance = -1; + x = _fromX = fromX; + y = _fromY = fromY; + _controlX = controlX; + _controlY = controlY; + _toX = toX; + _toY = toY; + _target = distance / speed; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + x = _fromX * (1 - _t) * (1 - _t) + _controlX * 2 * (1 - _t) * _t + _toX * _t * _t; + y = _fromY * (1 - _t) * (1 - _t) + _controlY * 2 * (1 - _t) * _t + _toY * _t * _t; + } + + /** + * The distance of the entire curve. + */ + public function get distance():Number + { + if (_distance >= 0) return _distance; + var a:Point = FP.point, + b:Point = FP.point2; + a.x = x - 2 * _controlX + _toX; + a.y = y - 2 * _controlY + _toY; + b.x = 2 * _controlX - 2 * x; + b.y = 2 * _controlY - 2 * y; + var A:Number = 4 * (a.x * a.x + a.y * a.y), + B:Number = 4 * (a.x * b.x + a.y * b.y), + C:Number = b.x * b.x + b.y * b.y, + ABC:Number = 2 * Math.sqrt(A + B + C), + A2:Number = Math.sqrt(A), + A32:Number = 2 * A * A2, + C2:Number = 2 * Math.sqrt(C), + BA:Number = B / A2; + return (A32 * ABC + A2 * B * (ABC - C2) + (4 * C * A - B * B) * Math.log((2 * A2 + BA + ABC) / (BA + C2))) / (4 * A32); + } + + // Curve information. + /** @private */ private var _distance:Number = -1; + /** @private */ private var _fromX:Number = 0; + /** @private */ private var _fromY:Number = 0; + /** @private */ private var _toX:Number = 0; + /** @private */ private var _toY:Number = 0; + /** @private */ private var _controlX:Number = 0; + /** @private */ private var _controlY:Number = 0; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/motion/QuadPath.as b/src/net/flashpunk/tweens/motion/QuadPath.as new file mode 100644 index 0000000..fdb2efa --- /dev/null +++ b/src/net/flashpunk/tweens/motion/QuadPath.as @@ -0,0 +1,192 @@ +package net.flashpunk.tweens.motion +{ + import flash.geom.Point; + import net.flashpunk.FP; + + /** + * A series of points which will determine a path from the + * beginning point to the end poing using quadratic curves. + */ + public class QuadPath extends Motion + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function QuadPath(complete:Function = null, type:uint = 0) + { + super(0, complete, type, null); + _curveT[0] = 0; + } + + /** + * Starts moving along the path. + * @param duration Duration of the movement. + * @param ease Optional easer function. + */ + public function setMotion(duration:Number, ease:Function = null):void + { + updatePath(); + _target = duration; + _speed = _distance / duration; + _ease = ease; + start(); + } + + /** + * Starts moving along the path at the speed. + * @param speed Speed of the movement. + * @param ease Optional easer function. + */ + public function setMotionSpeed(speed:Number, ease:Function = null):void + { + updatePath(); + _target = _distance / speed; + _speed = speed; + _ease = ease; + start(); + } + + /** + * Adds the point to the path. + * @param x X position. + * @param y Y position. + */ + public function addPoint(x:Number = 0, y:Number = 0):void + { + _updateCurve = true; + if (!_points.length) _curve[0] = new Point(x, y); + _points[_points.length] = new Point(x, y); + } + + /** + * Gets the point on the path. + * @param index Index of the point. + * @return The Point object. + */ + public function getPoint(index:uint = 0):Point + { + if (!_points.length) throw new Error("No points have been added to the path yet."); + return _points[index % _points.length]; + } + + /** @private Starts the Tween. */ + override public function start():void + { + _index = 0; + super.start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + if (_index < _curve.length - 1) + { + while (_t > _curveT[_index + 1]) _index ++; + } + var td:Number = _curveT[_index], + tt:Number = _curveT[_index + 1] - td; + td = (_t - td) / tt; + _a = _curve[_index]; + _b = _points[_index + 1]; + _c = _curve[_index + 1]; + x = _a.x * (1 - td) * (1 - td) + _b.x * 2 * (1 - td) * td + _c.x * td * td; + y = _a.y * (1 - td) * (1 - td) + _b.y * 2 * (1 - td) * td + _c.y * td * td; + } + + /** @private Updates the path, preparing the curve. */ + private function updatePath():void + { + if (_points.length < 3) throw new Error("A QuadPath must have at least 3 points to operate."); + if (!_updateCurve) return; + _updateCurve = false; + + // produce the curve points + var p:Point, + c:Point, + l:Point = _points[1], + i:uint = 2; + while (i < _points.length) + { + p = _points[i]; + if (_curve.length > i - 1) c = _curve[i - 1]; + else c = _curve[i - 1] = new Point; + if (i < _points.length - 1) + { + c.x = l.x + (p.x - l.x) / 2; + c.y = l.y + (p.y - l.y) / 2; + } + else + { + c.x = p.x; + c.y = p.y; + } + l = p; + i ++; + } + + // find the total distance of the path + i = 0; + _distance = 0; + while (i < _curve.length - 1) + { + _curveD[i] = curveLength(_curve[i], _points[i + 1], _curve[i + 1]); + _distance += _curveD[i ++]; + } + + // find t for each point on the curve + i = 1; + var d:Number = 0; + while (i < _curve.length - 1) + { + d += _curveD[i]; + _curveT[i ++] = d / _distance; + } + _curveT[_curve.length - 1] = 1; + } + + /** + * Amount of points on the path. + */ + public function get pointCount():Number { return _points.length; } + + /** @private Calculates the lenght of the curve. */ + private function curveLength(start:Point, control:Point, finish:Point):Number + { + var a:Point = FP.point, + b:Point = FP.point2; + a.x = start.x - 2 * control.x + finish.x; + a.y = start.y - 2 * control.y + finish.y; + b.x = 2 * control.x - 2 * start.x; + b.y = 2 * control.y - 2 * start.y; + var A:Number = 4 * (a.x * a.x + a.y * a.y), + B:Number = 4 * (a.x * b.x + a.y * b.y), + C:Number = b.x * b.x + b.y * b.y, + ABC:Number = 2 * Math.sqrt(A + B + C), + A2:Number = Math.sqrt(A), + A32:Number = 2 * A * A2, + C2:Number = 2 * Math.sqrt(C), + BA:Number = B / A2; + return (A32 * ABC + A2 * B * (ABC - C2) + (4 * C * A - B * B) * Math.log((2 * A2 + BA + ABC) / (BA + C2))) / (4 * A32); + } + + // Path information. + /** @private */ private var _points:Vector. = new Vector.; + /** @private */ private var _distance:Number = 0; + /** @private */ private var _speed:Number = 0; + /** @private */ private var _index:uint = 0; + + // Curve information. + /** @private */ private var _updateCurve:Boolean = true; + /** @private */ private var _curve:Vector. = new Vector.; + /** @private */ private var _curveT:Vector. = new Vector.; + /** @private */ private var _curveD:Vector. = new Vector.; + + // Curve points. + /** @private */ private var _a:Point; + /** @private */ private var _b:Point; + /** @private */ private var _c:Point; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/sound/Fader.as b/src/net/flashpunk/tweens/sound/Fader.as new file mode 100644 index 0000000..02b4281 --- /dev/null +++ b/src/net/flashpunk/tweens/sound/Fader.as @@ -0,0 +1,48 @@ +package net.flashpunk.tweens.sound +{ + import net.flashpunk.FP; + import net.flashpunk.Tween; + + /** + * Global volume fader. + */ + public class Fader extends Tween + { + /** + * Constructor. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function Fader(complete:Function = null, type:uint = 0) + { + super(0, type, complete); + } + + /** + * Fades FP.volume to the target volume. + * @param volume The volume to fade to. + * @param duration Duration of the fade. + * @param ease Optional easer function. + */ + public function fadeTo(volume:Number, duration:Number, ease:Function = null):void + { + if (volume < 0) volume = 0; + _start = FP.volume; + _range = volume - _start; + _target = duration; + _ease = ease; + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + FP.volume = _start + _range * _t; + } + + // Fader information. + /** @private */ private var _start:Number; + /** @private */ private var _range:Number; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/tweens/sound/SfxFader.as b/src/net/flashpunk/tweens/sound/SfxFader.as new file mode 100644 index 0000000..67f0548 --- /dev/null +++ b/src/net/flashpunk/tweens/sound/SfxFader.as @@ -0,0 +1,94 @@ +package net.flashpunk.tweens.sound +{ + import net.flashpunk.Sfx; + import net.flashpunk.Tween; + + /** + * Sound effect fader. + */ + public class SfxFader extends Tween + { + /** + * Constructor. + * @param sfx The Sfx object to alter. + * @param complete Optional completion callback. + * @param type Tween type. + */ + public function SfxFader(sfx:Sfx, complete:Function = null, type:uint = 0) + { + super(0, type, finish); + _complete = complete; + _sfx = sfx; + } + + /** + * Fades the Sfx to the target volume. + * @param volume The volume to fade to. + * @param duration Duration of the fade. + * @param ease Optional easer function. + */ + public function fadeTo(volume:Number, duration:Number, ease:Function = null):void + { + if (volume < 0) volume = 0; + _start = _sfx.volume; + _range = volume - _start; + _target = duration; + _ease = ease; + start(); + } + + /** + * Fades out the Sfx, while also playing and fading in a replacement Sfx. + * @param play The Sfx to play and fade in. + * @param loop If the new Sfx should loop. + * @param duration Duration of the crossfade. + * @param volume The volume to fade in the new Sfx to. + * @param ease Optional easer function. + */ + public function crossFade(play:Sfx, loop:Boolean, duration:Number, volume:Number = 1, ease:Function = null):void + { + _crossSfx = play; + _crossRange = volume; + _start = _sfx.volume; + _range = -_start; + _target = duration; + _ease = ease; + if (loop) _crossSfx.loop(0); + else _crossSfx.play(0); + start(); + } + + /** @private Updates the Tween. */ + override public function update():void + { + super.update(); + if (_sfx) _sfx.volume = _start + _range * _t; + if (_crossSfx) _crossSfx.volume = _crossRange * _t; + } + + /** @private When the tween completes. */ + private function finish():void + { + if (_crossSfx) + { + if (_sfx) _sfx.stop(); + _sfx = _crossSfx; + _crossSfx = null; + } + if (_complete != null) _complete(); + } + + /** + * The current Sfx this object is effecting. + */ + public function get sfx():Sfx { return _sfx; } + + // Fader information. + /** @private */ private var _sfx:Sfx; + /** @private */ private var _start:Number; + /** @private */ private var _range:Number; + /** @private */ private var _crossSfx:Sfx; + /** @private */ private var _crossRange:Number; + /** @private */ private var _complete:Function; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/utils/Data.as b/src/net/flashpunk/utils/Data.as new file mode 100644 index 0000000..ce69c2b --- /dev/null +++ b/src/net/flashpunk/utils/Data.as @@ -0,0 +1,146 @@ +package net.flashpunk.utils +{ + import flash.net.SharedObject; + + /** + * Static helper class used for saving and loading data from stored cookies. + */ + public class Data + { + /** + * If you want to share data between different SWFs on the same host, use this id. + */ + public static var id:String = ""; + + /** + * Overwrites the current data with the file. + * @param file The filename to load. + */ + public static function load(file:String = ""):void + { + var data:Object = loadData(file); + _data = { }; + for (var i:String in data) _data[i] = data[i]; + } + + /** + * Overwrites the file with the current data. The current data will not be saved until this function is called. + * @param file The filename to save. + */ + public static function save(file:String = ""):void + { + if (_shared) _shared.clear(); + var data:Object = loadData(file); + for (var i:String in _data) data[i] = _data[i]; + _shared.flush(SIZE); + } + + /** + * Reads an int from the current data. + * @param name Property to read. + * @param defaultValue Default value. + * @return The property value, or defaultValue if the property is not assigned. + */ + public static function readInt(name:String, defaultValue:int = 0):int + { + return int(read(name, defaultValue)); + } + + /** + * Reads a uint from the current data. + * @param name Property to read. + * @param defaultValue Default value. + * @return The property value, or defaultValue if the property is not assigned. + */ + public static function readUint(name:String, defaultValue:uint = 0):uint + { + return uint(read(name, defaultValue)); + } + + /** + * Reads a Boolean from the current data. + * @param name Property to read. + * @param defaultValue Default value. + * @return The property value, or defaultValue if the property is not assigned. + */ + public static function readBool(name:String, defaultValue:Boolean = true):Boolean + { + return Boolean(read(name, defaultValue)); + } + + /** + * Reads a String from the current data. + * @param name Property to read. + * @param defaultValue Default value. + * @return The property value, or defaultValue if the property is not assigned. + */ + public static function readString(name:String, defaultValue:String = ""):String + { + return String(read(name, defaultValue)); + } + + /** + * Writes an int to the current data. + * @param name Property to write. + * @param value Value to write. + */ + public static function writeInt(name:String, value:int = 0):void + { + _data[name] = value; + } + + /** + * Writes a uint to the current data. + * @param name Property to write. + * @param value Value to write. + */ + public static function writeUint(name:String, value:uint = 0):void + { + _data[name] = value; + } + + /** + * Writes a Boolean to the current data. + * @param name Property to write. + * @param value Value to write. + */ + public static function writeBool(name:String, value:Boolean = true):void + { + _data[name] = value; + } + + /** + * Writes a String to the current data. + * @param name Property to write. + * @param value Value to write. + */ + public static function writeString(name:String, value:String = ""):void + { + _data[name] = value; + } + + /** @private Reads a property from the data object. */ + private static function read(name:String, defaultValue:*):* + { + if (_data.hasOwnProperty(name)) return _data[name]; + return defaultValue; + } + + /** @private Loads the data file, or return it if you're loading the same one. */ + private static function loadData(file:String):Object + { + if (!file) file = DEFAULT_FILE; + if (id) _shared = SharedObject.getLocal(PREFIX + "/" + id + "/" + file, "/"); + else _shared = SharedObject.getLocal(PREFIX + "/" + file); + return _shared.data; + } + + // Data information. + /** @private */ private static var _shared:SharedObject; + /** @private */ private static var _dir:String; + /** @private */ private static var _data:Object = { }; + /** @private */ private static const PREFIX:String = "FlashPunk"; + /** @private */ private static const DEFAULT_FILE:String = "_file"; + /** @private */ private static const SIZE:uint = 10000; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/utils/Draw.as b/src/net/flashpunk/utils/Draw.as new file mode 100644 index 0000000..6b20162 --- /dev/null +++ b/src/net/flashpunk/utils/Draw.as @@ -0,0 +1,375 @@ +package net.flashpunk.utils +{ + import flash.display.BitmapData; + import flash.display.Graphics; + import flash.display.LineScaleMode; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import net.flashpunk.Entity; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + + /** + * Static class with access to miscellanious drawing functions. + * These functions are not meant to replace Graphic components + * for Entities, but rather to help with testing and debugging. + */ + public class Draw + { + /** + * The blending mode used by Draw functions. This will not + * apply to Draw.line(), but will apply to Draw.linePlus(). + */ + public static var blend:String; + + /** + * Sets the drawing target for Draw functions. + * @param target The buffer to draw to. + * @param camera The camera offset (use null for none). + * @param blend The blend mode to use. + */ + public static function setTarget(target:BitmapData, camera:Point = null, blend:String = null):void + { + _target = target; + _camera = camera ? camera : FP.zero; + Draw.blend = blend; + } + + /** + * Resets the drawing target to the default. The same as calling Draw.setTarget(FP.buffer, FP.camera). + */ + public static function resetTarget():void + { + _target = FP.buffer; + _camera = FP.camera; + Draw.blend = null; + } + + /** + * Draws a pixelated, non-antialiased line. + * @param x1 Starting x position. + * @param y1 Starting y position. + * @param x2 Ending x position. + * @param y2 Ending y position. + * @param color Color of the line. + */ + public static function line(x1:int, y1:int, x2:int, y2:int, color:uint = 0xFFFFFF):void + { + if (color < 0xFF000000) color = 0xFF000000 | color; + + // get the drawing positions + x1 -= _camera.x; + y1 -= _camera.y; + x2 -= _camera.x; + y2 -= _camera.y; + + // get the drawing difference + var screen:BitmapData = _target, + X:Number = Math.abs(x2 - x1), + Y:Number = Math.abs(y2 - y1), + xx:int, + yy:int; + + // draw a single pixel + if (X == 0) + { + if (Y == 0) + { + screen.setPixel32(x1, y1, color); + return; + } + // draw a straight vertical line + yy = y2 > y1 ? 1 : -1; + while (y1 != y2) + { + screen.setPixel32(x1, y1, color); + y1 += yy; + } + screen.setPixel32(x2, y2, color); + return; + } + + if (Y == 0) + { + // draw a straight horizontal line + xx = x2 > x1 ? 1 : -1; + while (x1 != x2) + { + screen.setPixel32(x1, y1, color); + x1 += xx; + } + screen.setPixel32(x2, y2, color); + return; + } + + xx = x2 > x1 ? 1 : -1; + yy = y2 > y1 ? 1 : -1; + var c:Number = 0, + slope:Number; + + if (X > Y) + { + slope = Y / X; + c = .5; + while (x1 != x2) + { + screen.setPixel32(x1, y1, color); + x1 += xx; + c += slope; + if (c >= 1) + { + y1 += yy; + c -= 1; + } + } + screen.setPixel32(x2, y2, color); + } + else + { + slope = X / Y; + c = .5; + while (y1 != y2) + { + screen.setPixel32(x1, y1, color); + y1 += yy; + c += slope; + if (c >= 1) + { + x1 += xx; + c -= 1; + } + } + screen.setPixel32(x2, y2, color); + } + } + + /** + * Draws a smooth, antialiased line with optional alpha and thickness. + * @param x1 Starting x position. + * @param y1 Starting y position. + * @param x2 Ending x position. + * @param y2 Ending y position. + * @param color Color of the line. + * @param alpha Alpha of the line. + * @param thick The thickness of the line. + */ + public static function linePlus(x1:int, y1:int, x2:int, y2:int, color:uint = 0xFF000000, alpha:Number = 1, thick:Number = 1):void + { + _graphics.clear(); + _graphics.lineStyle(thick, color, alpha, false, LineScaleMode.NONE); + _graphics.moveTo(x1 - _camera.x, y1 - _camera.y); + _graphics.lineTo(x2 - _camera.x, y2 - _camera.y); + _target.draw(FP.sprite, null, null, blend); + } + + /** + * Draws a filled rectangle. + * @param x X position of the rectangle. + * @param y Y position of the rectangle. + * @param width Width of the rectangle. + * @param height Height of the rectangle. + * @param color Color of the rectangle. + * @param alpha Alpha of the rectangle. + */ + public static function rect(x:int, y:int, width:uint, height:uint, color:uint = 0xFFFFFF, alpha:Number = 1):void + { + if (alpha >= 1 && !blend) + { + if (color < 0xFF000000) color = 0xFF000000 | color; + _rect.x = x - _camera.x; + _rect.y = y - _camera.y; + _rect.width = width; + _rect.height = height; + _target.fillRect(_rect, color); + return; + } + if (color >= 0xFF000000) color = 0xFFFFFF & color; + _graphics.clear(); + _graphics.beginFill(color, alpha); + _graphics.drawRect(x - _camera.x, y - _camera.y, width, height); + _target.draw(FP.sprite, null, null, blend); + } + + /** + * Draws a non-filled, pixelated circle. + * @param x Center x position. + * @param y Center y position. + * @param radius Radius of the circle. + * @param color Color of the circle. + */ + public static function circle(x:int, y:int, radius:int, color:uint = 0xFFFFFF):void + { + if (color < 0xFF000000) color = 0xFF000000 | color; + x -= _camera.x; + y -= _camera.y; + var f:int = 1 - radius, + fx:int = 1, + fy:int = -2 * radius, + xx:int = 0, + yy:int = radius; + _target.setPixel32(x, y + radius, color); + _target.setPixel32(x, y - radius, color); + _target.setPixel32(x + radius, y, color); + _target.setPixel32(x - radius, y, color); + while (xx < yy) + { + if (f >= 0) + { + yy --; + fy += 2; + f += fy; + } + xx ++; + fx += 2; + f += fx; + _target.setPixel32(x + xx, y + yy, color); + _target.setPixel32(x - xx, y + yy, color); + _target.setPixel32(x + xx, y - yy, color); + _target.setPixel32(x - xx, y - yy, color); + _target.setPixel32(x + yy, y + xx, color); + _target.setPixel32(x - yy, y + xx, color); + _target.setPixel32(x + yy, y - xx, color); + _target.setPixel32(x - yy, y - xx, color); + } + } + + /** + * Draws a circle to the screen. + * @param x X position of the circle's center. + * @param y Y position of the circle's center. + * @param radius Radius of the circle. + * @param color Color of the circle. + * @param alpha Alpha of the circle. + * @param fill If the circle should be filled with the color (true) or just an outline (false). + * @param thick How thick the outline should be (only applicable when fill = false). + */ + public static function circlePlus(x:int, y:int, radius:Number, color:uint = 0xFFFFFF, alpha:Number = 1, fill:Boolean = true, thick:int = 1):void + { + _graphics.clear(); + if (fill) + { + _graphics.beginFill(color & 0xFFFFFF, alpha); + _graphics.drawCircle(x - _camera.x, y - _camera.y, radius); + _graphics.endFill(); + } + else + { + _graphics.lineStyle(thick, color & 0xFFFFFF, alpha); + _graphics.drawCircle(x - _camera.x, y - _camera.y, radius); + } + _target.draw(FP.sprite, null, null, blend); + } + + /** + * Draws the Entity's hitbox. + * @param e The Entity whose hitbox is to be drawn. + * @param outline If just the hitbox's outline should be drawn. + * @param color Color of the hitbox. + * @param alpha Alpha of the hitbox. + */ + public static function hitbox(e:Entity, outline:Boolean = true, color:uint = 0xFFFFFF, alpha:Number = 1):void + { + if (outline) + { + if (color < 0xFF000000) color = 0xFF000000 | color; + var x:int = e.x - e.originX - _camera.x, + y:int = e.y - e.originY - _camera.y; + _rect.x = x; + _rect.y = y; + _rect.width = e.width; + _rect.height = 1; + _target.fillRect(_rect, color); + _rect.y += e.height - 1; + _target.fillRect(_rect, color); + _rect.y = y; + _rect.width = 1; + _rect.height = e.height; + _target.fillRect(_rect, color); + _rect.x += e.width - 1; + _target.fillRect(_rect, color); + return; + } + if (alpha >= 1 && !blend) + { + if (color < 0xFF000000) color = 0xFF000000 | color; + _rect.x = e.x - e.originX - _camera.x; + _rect.y = e.y - e.originY - _camera.y; + _rect.width = e.width; + _rect.height = e.height; + _target.fillRect(_rect, color); + return; + } + if (color >= 0xFF000000) color = 0xFFFFFF & color; + _graphics.clear(); + _graphics.beginFill(color, alpha); + _graphics.drawRect(e.x - e.originX - _camera.x, e.y - e.originY - _camera.y, e.width, e.height); + _target.draw(FP.sprite, null, null, blend); + } + + /** + * Draws a quadratic curve. + * @param x1 X start. + * @param y1 Y start. + * @param x2 X control point, used to determine the curve. + * @param y2 Y control point, used to determine the curve. + * @param x3 X finish. + * @param y3 Y finish. + * @param color Color of the curve + * @param alpha Alpha transparency. + */ + public static function curve(x1:int, y1:int, x2:int, y2:int, x3:int, y3:int, thick:Number = 1, color:uint = 0, alpha:Number = 1):void + { + _graphics.clear(); + _graphics.lineStyle(thick, color, alpha); + _graphics.moveTo(x1 - _camera.x, y1 - _camera.y); + _graphics.curveTo(x2 - _camera.x, y2 - _camera.y, x3 - _camera.x, y3 - _camera.y); + _target.draw(FP.sprite, null, null, blend); + } + + /** + * Draws a graphic object. + * @param g The Graphic to draw. + * @param x X position. + * @param y Y position. + */ + public static function graphic(g:Graphic, x:int = 0, y:int = 0):void + { + if (g.visible) + { + if (g.relative) + { + FP.point.x = x; + FP.point.y = y; + } + else FP.point.x = FP.point.y = 0; + FP.point2.x = FP.camera.x; + FP.point2.y = FP.camera.y; + g.render(_target, FP.point, FP.point2); + } + } + + /** + * Draws an Entity object. + * @param e The Entity to draw. + * @param x X position. + * @param y Y position. + * @param addEntityPosition Adds the Entity's x and y position to the target position. + */ + public static function entity(e:Entity, x:int = 0, y:int = 0, addEntityPosition:Boolean = false):void + { + if (e.visible && e.graphic) + { + if (addEntityPosition) graphic(e.graphic, x + e.x, y + e.y); + else graphic(e.graphic, x, y); + } + } + + // Drawing information. + /** @private */ private static var _target:BitmapData; + /** @private */ private static var _camera:Point; + /** @private */ private static var _graphics:Graphics = FP.sprite.graphics; + /** @private */ private static var _rect:Rectangle = FP.rect; + /** @private */ private static var _matrix:Matrix = new Matrix; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/utils/Ease.as b/src/net/flashpunk/utils/Ease.as new file mode 100644 index 0000000..b3c690d --- /dev/null +++ b/src/net/flashpunk/utils/Ease.as @@ -0,0 +1,214 @@ +package net.flashpunk.utils +{ + /** + * Static class with useful easer functions that can be used by Tweens. + */ + public class Ease + { + /** Quadratic in. */ + public static function quadIn(t:Number):Number + { + return t * t; + } + + /** Quadratic out. */ + public static function quadOut(t:Number):Number + { + return -t * (t - 2); + } + + /** Quadratic in and out. */ + public static function quadInOut(t:Number):Number + { + return t <= .5 ? t * t * 2 : 1 - (--t) * t * 2; + } + + /** Cubic in. */ + public static function cubeIn(t:Number):Number + { + return t * t * t; + } + + /** Cubic out. */ + public static function cubeOut(t:Number):Number + { + return 1 + (--t) * t * t; + } + + /** Cubic in and out. */ + public static function cubeInOut(t:Number):Number + { + return t <= .5 ? t * t * t * 4 : 1 + (--t) * t * t * 4; + } + + /** Quart in. */ + public static function quartIn(t:Number):Number + { + return t * t * t * t; + } + + /** Quart out. */ + public static function quartOut(t:Number):Number + { + return 1 - (t-=1) * t * t * t; + } + + /** Quart in and out. */ + public static function quartInOut(t:Number):Number + { + return t <= .5 ? t * t * t * t * 8 : (1 - (t = t * 2 - 2) * t * t * t) / 2 + .5; + } + + /** Quint in. */ + public static function quintIn(t:Number):Number + { + return t * t * t * t * t; + } + + /** Quint out. */ + public static function quintOut(t:Number):Number + { + return (t = t - 1) * t * t * t * t + 1; + } + + /** Quint in and out. */ + public static function quintInOut(t:Number):Number + { + return ((t *= 2) < 1) ? (t * t * t * t * t) / 2 : ((t -= 2) * t * t * t * t + 2) / 2; + } + + /** Sine in. */ + public static function sineIn(t:Number):Number + { + return -Math.cos(PI2 * t) + 1; + } + + /** Sine out. */ + public static function sineOut(t:Number):Number + { + return Math.sin(PI2 * t); + } + + /** Sine in and out. */ + public static function sineInOut(t:Number):Number + { + return -Math.cos(PI * t) / 2 + .5; + } + + /** Bounce in. */ + public static function bounceIn(t:Number):Number + { + t = 1 - t; + if (t < B1) return 1 - 7.5625 * t * t; + if (t < B2) return 1 - (7.5625 * (t - B3) * (t - B3) + .75); + if (t < B4) return 1 - (7.5625 * (t - B5) * (t - B5) + .9375); + return 1 - (7.5625 * (t - B6) * (t - B6) + .984375); + } + + /** Bounce out. */ + public static function bounceOut(t:Number):Number + { + if (t < B1) return 7.5625 * t * t; + if (t < B2) return 7.5625 * (t - B3) * (t - B3) + .75; + if (t < B4) return 7.5625 * (t - B5) * (t - B5) + .9375; + return 7.5625 * (t - B6) * (t - B6) + .984375; + } + + /** Bounce in and out. */ + public static function bounceInOut(t:Number):Number + { + if (t < .5) + { + t = 1 - t * 2; + if (t < B1) return (1 - 7.5625 * t * t) / 2; + if (t < B2) return (1 - (7.5625 * (t - B3) * (t - B3) + .75)) / 2; + if (t < B4) return (1 - (7.5625 * (t - B5) * (t - B5) + .9375)) / 2; + return (1 - (7.5625 * (t - B6) * (t - B6) + .984375)) / 2; + } + t = t * 2 - 1; + if (t < B1) return (7.5625 * t * t) / 2 + .5; + if (t < B2) return (7.5625 * (t - B3) * (t - B3) + .75) / 2 + .5; + if (t < B4) return (7.5625 * (t - B5) * (t - B5) + .9375) / 2 + .5; + return (7.5625 * (t - B6) * (t - B6) + .984375) / 2 + .5; + } + + /** Circle in. */ + public static function circIn(t:Number):Number + { + return -(Math.sqrt(1 - t * t) - 1); + } + + /** Circle out. */ + public static function circOut(t:Number):Number + { + return Math.sqrt(1 - (t - 1) * (t - 1)); + } + + /** Circle in and out. */ + public static function circInOut(t:Number):Number + { + return t <= .5 ? (Math.sqrt(1 - t * t * 4) - 1) / -2 : (Math.sqrt(1 - (t * 2 - 2) * (t * 2 - 2)) + 1) / 2; + } + + /** Exponential in. */ + public static function expoIn(t:Number):Number + { + return Math.pow(2, 10 * (t - 1)); + } + + /** Exponential out. */ + public static function expoOut(t:Number):Number + { + return -Math.pow(2, -10 * t) + 1; + } + + /** Exponential in and out. */ + public static function expoInOut(t:Number):Number + { + return t < .5 ? Math.pow(2, 10 * (t * 2 - 1)) / 2 : (-Math.pow(2, -10 * (t * 2 - 1)) + 2) / 2; + } + + /** Back in. */ + public static function backIn(t:Number):Number + { + return t * t * (2.70158 * t - 1.70158); + } + + /** Back out. */ + public static function backOut(t:Number):Number + { + return 1 - (--t) * (t) * (-2.70158 * t - 1.70158); + } + + /** Back in and out. */ + public static function backInOut(t:Number):Number + { + t *= 2; + if (t < 1) return t * t * (2.70158 * t - 1.70158) / 2; + t --; + return (1 - (--t) * (t) * (-2.70158 * t - 1.70158)) / 2 + .5; + } + + // Easing constants. + /** @private */ private static const PI:Number = Math.PI; + /** @private */ private static const PI2:Number = Math.PI / 2; + /** @private */ private static const EL:Number = 2 * PI / .45; + /** @private */ private static const B1:Number = 1 / 2.75; + /** @private */ private static const B2:Number = 2 / 2.75; + /** @private */ private static const B3:Number = 1.5 / 2.75; + /** @private */ private static const B4:Number = 2.5 / 2.75; + /** @private */ private static const B5:Number = 2.25 / 2.75; + /** @private */ private static const B6:Number = 2.625 / 2.75; + + /** + * Operation of in/out easers: + * + * in(t) + * return t; + * out(t) + * return 1 - in(1 - t); + * inOut(t) + * return (t <= .5) ? in(t * 2) / 2 : out(t * 2 - 1) / 2 + .5; + */ + } +} \ No newline at end of file diff --git a/src/net/flashpunk/utils/Input.as b/src/net/flashpunk/utils/Input.as new file mode 100644 index 0000000..6589a2f --- /dev/null +++ b/src/net/flashpunk/utils/Input.as @@ -0,0 +1,296 @@ +package net.flashpunk.utils +{ + import flash.display.Stage; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.ui.Keyboard; + import net.flashpunk.*; + + /** + * Static class updated by Engine. Use for defining and checking keyboard/mouse input. + */ + public class Input + { + /** + * An updated string containing the last 100 characters pressed on the keyboard. + * Useful for creating text input fields, such as highscore entries, etc. + */ + public static var keyString:String = ""; + + /** + * The last key pressed. + */ + public static var lastKey:int; + + /** + * If the mouse button is down. + */ + public static var mouseDown:Boolean = false; + + /** + * If the mouse button is up. + */ + public static var mouseUp:Boolean = true; + + /** + * If the mouse button was pressed this frame. + */ + public static var mousePressed:Boolean = false; + + /** + * If the mouse button was released this frame. + */ + public static var mouseReleased:Boolean = false; + + /** + * If the mouse wheel was moved this frame. + */ + public static var mouseWheel:Boolean = false; + + /** + * If the mouse wheel was moved this frame, this was the delta. + */ + public static function get mouseWheelDelta():int + { + if (mouseWheel) + { + mouseWheel = false; + return _mouseWheelDelta; + } + return 0; + } + + /** + * X position of the mouse on the screen. + */ + public static function get mouseX():int + { + return FP.screen.mouseX; + } + + /** + * Y position of the mouse on the screen. + */ + public static function get mouseY():int + { + return FP.screen.mouseY; + } + + /** + * The absolute mouse x position on the screen (unscaled). + */ + public static function get mouseFlashX():int + { + return FP.stage.mouseX; + } + + /** + * The absolute mouse y position on the screen (unscaled). + */ + public static function get mouseFlashY():int + { + return FP.stage.mouseY; + } + + /** + * Defines a new input. + * @param name String to map the input to. + * @param ...keys The keys to use for the Input. + */ + public static function define(name:String, ...keys):void + { + _control[name] = Vector.(keys); + } + + /** + * If the input or key is held down. + * @param input An input name or key to check for. + * @return True or false. + */ + public static function check(input:*):Boolean + { + if (input is String) + { + var v:Vector. = _control[input], + i:int = v.length; + while (i --) + { + if (v[i] < 0) + { + if (_keyNum > 0) return true; + continue; + } + if (_key[v[i]]) return true; + } + return false; + } + return input < 0 ? _keyNum > 0 : _key[input]; + } + + /** + * If the input or key was pressed this frame. + * @param input An input name or key to check for. + * @return True or false. + */ + public static function pressed(input:*):Boolean + { + if (input is String) + { + var v:Vector. = _control[input], + i:int = v.length; + while (i --) + { + if ((v[i] < 0) ? _pressNum : _press.indexOf(v[i]) >= 0) return true; + } + return false; + } + return (input < 0) ? _pressNum : _press.indexOf(input) >= 0; + } + + /** + * If the input or key was released this frame. + * @param input An input name or key to check for. + * @return True or false. + */ + public static function released(input:*):Boolean + { + if (input is String) + { + var v:Vector. = _control[input], + i:int = v.length; + while (i --) + { + if ((v[i] < 0) ? _releaseNum : _release.indexOf(v[i]) >= 0) return true; + } + return false; + } + return (input < 0) ? _releaseNum : _release.indexOf(input) >= 0; + } + + /** + * Returns the keys mapped to the input name. + * @param name The input name. + * @return A Vector of keys. + */ + public static function keys(name:String):Vector. + { + return _control[name] as Vector.; + } + + /** @private Called by Engine to enable keyboard input on the stage. */ + public static function enable():void + { + if (!_enabled && FP.stage) + { + FP.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); + FP.stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); + FP.stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + FP.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + FP.stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + _enabled = true; + } + } + + /** @private Called by Engine to update the input. */ + public static function update():void + { + while (_pressNum --) _press[_pressNum] = -1; + _pressNum = 0; + while (_releaseNum --) _release[_releaseNum] = -1; + _releaseNum = 0; + if (mousePressed) mousePressed = false; + if (mouseReleased) mouseReleased = false; + } + + /** + * Clears all input states. + */ + public static function clear():void + { + _press.length = _pressNum = 0; + _release.length = _releaseNum = 0; + var i:int = _key.length; + while (i --) _key[i] = false; + _keyNum = 0; + } + + /** @private Event handler for key press. */ + private static function onKeyDown(e:KeyboardEvent = null):void + { + // get the keycode + var code:int = lastKey = e.keyCode; + + // update the keystring + if (code == Key.BACKSPACE) keyString = keyString.substring(0, keyString.length - 1); + else if ((code > 47 && code < 58) || (code > 64 && code < 91) || code == 32) + { + if (keyString.length > KEYSTRING_MAX) keyString = keyString.substring(1); + var char:String = String.fromCharCode(code); + if (e.shiftKey || Keyboard.capsLock) char = char.toLocaleUpperCase(); + else char = char.toLocaleLowerCase(); + keyString += char; + } + + // update the keystate + if (!_key[code]) + { + _key[code] = true; + _keyNum ++; + _press[_pressNum ++] = code; + } + } + + /** @private Event handler for key release. */ + private static function onKeyUp(e:KeyboardEvent):void + { + // get the keycode and update the keystate + var code:int = e.keyCode; + if (_key[code]) + { + _key[code] = false; + _keyNum --; + _release[_releaseNum ++] = code; + } + } + + /** @private Event handler for mouse press. */ + private static function onMouseDown(e:MouseEvent):void + { + if (!mouseDown) + { + mouseDown = true; + mouseUp = false; + mousePressed = true; + } + } + + /** @private Event handler for mouse release. */ + private static function onMouseUp(e:MouseEvent):void + { + mouseDown = false; + mouseUp = true; + mouseReleased = true; + } + + /** @private Event handler for mouse wheel events */ + private static function onMouseWheel(e:MouseEvent):void + { + mouseWheel = true; + _mouseWheelDelta = e.delta; + } + + // Max amount of characters stored by the keystring. + /** @private */ private static const KEYSTRING_MAX:uint = 100; + + // Input information. + /** @private */ private static var _enabled:Boolean = false; + /** @private */ private static var _key:Vector. = new Vector.(256); + /** @private */ private static var _keyNum:int = 0; + /** @private */ private static var _press:Vector. = new Vector.(256); + /** @private */ private static var _release:Vector. = new Vector.(256); + /** @private */ private static var _pressNum:int = 0; + /** @private */ private static var _releaseNum:int = 0; + /** @private */ private static var _control:Object = {}; + /** @private */ private static var _mouseWheelDelta:int = 0; + } +} \ No newline at end of file diff --git a/src/net/flashpunk/utils/Key.as b/src/net/flashpunk/utils/Key.as new file mode 100644 index 0000000..144cc90 --- /dev/null +++ b/src/net/flashpunk/utils/Key.as @@ -0,0 +1,193 @@ +package net.flashpunk.utils +{ + /** + * Contains static key constants to be used by Input. + */ + public class Key + { + public static const ANY:int = -1; + + public static const LEFT:int = 37; + public static const UP:int = 38; + public static const RIGHT:int = 39; + public static const DOWN:int = 40; + + public static const ENTER:int = 13; + public static const CONTROL:int = 17; + public static const SPACE:int = 32; + public static const SHIFT:int = 16; + public static const BACKSPACE:int = 8; + public static const CAPS_LOCK:int = 20; + public static const DELETE:int = 46; + public static const END:int = 35; + public static const ESCAPE:int = 27; + public static const HOME:int = 36; + public static const INSERT:int = 45; + public static const TAB:int = 9; + public static const PAGE_DOWN:int = 34; + public static const PAGE_UP:int = 33; + public static const LEFT_SQUARE_BRACKET:int = 219; + public static const RIGHT_SQUARE_BRACKET:int = 221; + + public static const A:int = 65; + public static const B:int = 66; + public static const C:int = 67; + public static const D:int = 68; + public static const E:int = 69; + public static const F:int = 70; + public static const G:int = 71; + public static const H:int = 72; + public static const I:int = 73; + public static const J:int = 74; + public static const K:int = 75; + public static const L:int = 76; + public static const M:int = 77; + public static const N:int = 78; + public static const O:int = 79; + public static const P:int = 80; + public static const Q:int = 81; + public static const R:int = 82; + public static const S:int = 83; + public static const T:int = 84; + public static const U:int = 85; + public static const V:int = 86; + public static const W:int = 87; + public static const X:int = 88; + public static const Y:int = 89; + public static const Z:int = 90; + + public static const F1:int = 112; + public static const F2:int = 113; + public static const F3:int = 114; + public static const F4:int = 115; + public static const F5:int = 116; + public static const F6:int = 117; + public static const F7:int = 118; + public static const F8:int = 119; + public static const F9:int = 120; + public static const F10:int = 121; + public static const F11:int = 122; + public static const F12:int = 123; + public static const F13:int = 124; + public static const F14:int = 125; + public static const F15:int = 126; + + public static const DIGIT_0:int = 48; + public static const DIGIT_1:int = 49; + public static const DIGIT_2:int = 50; + public static const DIGIT_3:int = 51; + public static const DIGIT_4:int = 52; + public static const DIGIT_5:int = 53; + public static const DIGIT_6:int = 54; + public static const DIGIT_7:int = 55; + public static const DIGIT_8:int = 56; + public static const DIGIT_9:int = 57; + + public static const NUMPAD_0:int = 96; + public static const NUMPAD_1:int = 97; + public static const NUMPAD_2:int = 98; + public static const NUMPAD_3:int = 99; + public static const NUMPAD_4:int = 100; + public static const NUMPAD_5:int = 101; + public static const NUMPAD_6:int = 102; + public static const NUMPAD_7:int = 103; + public static const NUMPAD_8:int = 104; + public static const NUMPAD_9:int = 105; + public static const NUMPAD_ADD:int = 107; + public static const NUMPAD_DECIMAL:int = 110; + public static const NUMPAD_DIVIDE:int = 111; + public static const NUMPAD_ENTER:int = 108; + public static const NUMPAD_MULTIPLY:int = 106; + public static const NUMPAD_SUBTRACT:int = 109; + + /** + * Returns the name of the key. + * @param char The key to name. + * @return The name. + */ + public static function name(char:int):String + { + if (char >= A && char <= Z) return String.fromCharCode(char); + if (char >= F1 && char <= F15) return "F" + String(char - 111); + if (char >= 96 && char <= 105) return "NUMPAD " + String(char - 96); + switch (char) + { + case LEFT: + return "LEFT"; + + case UP: + return "UP"; + + case RIGHT: + return "RIGHT"; + + case DOWN: + return "DOWN"; + + case ENTER: + return "ENTER"; + + case CONTROL: + return "CONTROL"; + + case SPACE: + return "SPACE"; + + case SHIFT: + return "SHIFT"; + + case BACKSPACE: + return "BACKSPACE"; + + case CAPS_LOCK: + return "CAPS LOCK"; + + case DELETE: + return "DELETE"; + + case END: + return "END"; + + case ESCAPE: + return "ESCAPE"; + + case HOME: + return "HOME"; + + case INSERT: + return "INSERT"; + + case TAB: + return "TAB"; + + case PAGE_DOWN: + return "PAGE DOWN"; + + case PAGE_UP: + return "PAGE UP"; + + case NUMPAD_ADD: + return "NUMPAD ADD"; + + case NUMPAD_DECIMAL: + return "NUMPAD DECIMAL"; + + case NUMPAD_DIVIDE: + return "NUMPAD DIVIDE"; + + case NUMPAD_ENTER: + return "NUMPAD ENTER"; + + case NUMPAD_MULTIPLY: + return "NUMPAD MULTIPLY"; + + case NUMPAD_SUBTRACT: + return "NUMPAD SUBTRACT"; + + default: + return String.fromCharCode(char); + } + return String.fromCharCode(char); + } + } +} \ No newline at end of file diff --git a/src/splash/Splash.as b/src/splash/Splash.as new file mode 100644 index 0000000..5aff446 --- /dev/null +++ b/src/splash/Splash.as @@ -0,0 +1,219 @@ +package splash +{ + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.display.GradientType; + import flash.display.Graphics; + import net.flashpunk.Entity; + import net.flashpunk.FP; + import net.flashpunk.Graphic; + import net.flashpunk.graphics.Graphiclist; + import net.flashpunk.graphics.Image; + import net.flashpunk.tweens.misc.NumTween; + import net.flashpunk.utils.Ease; + import net.flashpunk.World; + + /** + * This object displays the FlashPunk splash screen. + */ + public class Splash extends Entity + { + /** + * Embedded graphics. + */ + [Embed(source = 'splash_lines.png')] private const SPLASH_LINES:Class; + [Embed(source = 'splash_cog.png')] private const SPLASH_COG:Class; + [Embed(source = 'splash_left.png')] private const SPLASH_LEFT:Class; + [Embed(source = 'splash_right.png')] private const SPLASH_RIGHT:Class; + + /** + * Image objects. + */ + public var list:Graphiclist; + public var lines:Image; + public var cog:Image = new Image(SPLASH_COG); + public var leftText:Image = new Image(SPLASH_LEFT); + public var rightText:Image = new Image(SPLASH_RIGHT); + public var fade:Image = Image.createRect(FP.width, FP.height, 0); + + /** + * Tween information. + */ + public var tween:NumTween = new NumTween(tweenEnd); + public var fader:NumTween = new NumTween(faderEnd); + public var leftX:int; + public var rightX:int; + + /** + * Constructor, start the splash animation. + */ + public function Splash(color:uint = 0xFF3366, bgColor:uint = 0x202020, fadeTime:Number = .5, spinTime:Number = 2, spinPause:Number = .5, spins:Number = 720) + { + // Create the lines image. + var data:BitmapData = new BitmapData(FP.width, FP.height, false, 0x353535), + g:Graphics = FP.sprite.graphics; + g.clear(); + g.beginGradientFill(GradientType.RADIAL, [0, 0], [1, 0], [0, 255]); + g.drawCircle(0, 0, 100); + FP.matrix.identity(); + FP.matrix.scale(FP.width / 200, FP.height / 200); + FP.matrix.translate(FP.width / 2, FP.height / 2); + data.draw(FP.sprite, FP.matrix); + g.clear(); + g.beginBitmapFill((new SPLASH_LINES).bitmapData); + g.drawRect(0, 0, FP.width, FP.height); + data.draw(FP.sprite); + lines = new Image(data); + + // Set the entity information. + x = FP.width / 2; + y = FP.height / 2; + graphic = new Graphiclist(leftText, rightText, cog, lines, fade); + + // Set the screen information. + FP.screen.color = bgColor; + + // Set the lines properties. + lines.blend = BlendMode.SUBTRACT; + lines.smooth = true; + lines.x -= x; + lines.y -= y; + + // Set the big cog properties. + cog.visible = true; + cog.color = color; + cog.smooth = true; + cog.originX = cog.width / 2; + cog.originY = cog.height / 2; + cog.x -= cog.originX; + cog.y -= cog.originY; + + // Set the left text properties. + leftText.color = color; + leftText.smooth = true; + leftText.originX = leftText.width; + leftText.originY = leftText.height / 2; + leftText.x -= leftText.originX + cog.width / 4 + 4; + leftText.y -= leftText.originY; + leftX = leftText.x; + + // Set the right text properties. + rightText.color = color; + rightText.smooth = true; + rightText.originY = rightText.height / 2; + rightText.x += cog.width / 4; + rightText.y -= rightText.originY; + rightX = rightText.x; + + // Set the fade cover properties. + fade.x -= x; + fade.y -= y; + + // Set the timing properties. + _fadeTime = fadeTime; + _spinTime = spinTime; + _spinPause = spinPause; + _spins = spins; + + // Add the tweens. + addTween(tween); + addTween(fader); + + // Make invisible until you start it. + visible = false; + } + + /** + * Start the splash screen. + */ + public function start(onComplete:* = null):void + { + _onComplete = onComplete; + visible = true; + fadeIn(); + } + + /** + * Update the splash screen. + */ + override public function update():void + { + // Text scaling/positioning. + var t:Number = 1 - tween.scale; + leftText.x = leftX - t * FP.width / 2; + rightText.x = rightX + t * FP.width / 2; + leftText.scaleY = rightText.scaleY = tween.scale; + leftText.alpha = rightText.alpha = Ease.cubeIn(tween.scale); + + // Cog rotation/positioning. + cog.angle = tween.scale <= 1 ? tween.value : tween.value * 2; + cog.scale = 2.5 - tween.scale * 2; + cog.alpha = tween.scale; + + // Fade in/out alpha control. + fade.alpha = fader.value; + + // Pause before fade out. + if (_spinWait > 0) + { + _spinWait -= FP.fixed ? 1 : FP.elapsed; + if (_spinWait <= 0) fadeOut(); + } + } + + /** + * When the fade tween completes. + */ + private function faderEnd():void + { + if (fader.value == 0) tween.tween(_spins, 0, _spinTime, Ease.backOut); + else splashEnd(); + } + + /** + * When the tween completes. + */ + private function tweenEnd():void + { + if (_spinPause >= 0) _spinWait = _spinPause; + else fadeOut(); + } + + /** + * When the splash screen has completed. + */ + private function splashEnd():void + { + if (_onComplete == null) return; + else if (_onComplete is Function) _onComplete(); + else if (_onComplete is World) FP.world = _onComplete; + else throw new Error("The onComplete parameter must be a Function callback or World object."); + } + + /** + * Fades the splash screen in. + */ + private function fadeIn():void + { + fader.tween(1, 0, _fadeTime, Ease.cubeOut); + } + + /** + * Fades the splash screen out. + */ + private function fadeOut():void + { + fader.tween(0, 1, _fadeTime, Ease.cubeIn); + } + + /** + * Fade in/out time and logo spinning time. + */ + private var _fadeTime:Number; + private var _spinTime:Number; + private var _spins:Number; + private var _spinPause:Number; + private var _spinWait:Number = 0; + private var _onComplete:*; + } +} \ No newline at end of file diff --git a/src/splash/splash_cog.png b/src/splash/splash_cog.png new file mode 100644 index 0000000..8d922ad Binary files /dev/null and b/src/splash/splash_cog.png differ diff --git a/src/splash/splash_left.png b/src/splash/splash_left.png new file mode 100644 index 0000000..bb4dabf Binary files /dev/null and b/src/splash/splash_left.png differ diff --git a/src/splash/splash_lines.png b/src/splash/splash_lines.png new file mode 100644 index 0000000..d087e75 Binary files /dev/null and b/src/splash/splash_lines.png differ diff --git a/src/splash/splash_right.png b/src/splash/splash_right.png new file mode 100644 index 0000000..8285c00 Binary files /dev/null and b/src/splash/splash_right.png differ