Zooming an object based on mouse position - actionscript-3

I've got a large large Sprite, and I want to zoom in and out keeping mouse position as the pivot point, exactly how google maps or the photoshop zoom works, when you rotate the mouse wheel.
To archieve the zoom effect I tween the scaleX and scaleY properties, but the fixed point is (0,0), while i need to be the mouse position (that changes everytime, of course).
How can I change this?
thanks.

I just did a Google search for "zoom about an arbitrary point" (minus the quotes) and the first result looks promising.
You have to take the original offset out the scale and then reapply it at the new scale. I've done this myself but don't have the code to hand right now, so I'll see if I can dig it out and post it later.
The pseudo code for this is something like this (from memory):
float dir = UP ? 1 : -1;
float oldXscale = Xscale;
float oldYscale = Yscale;
Xscale += dir * increment;
Yscale += dir * increment;
newX = (oldX - Xoffset) / Xscale;
newY = (oldY - Yoffset) / Yscale;
Xoffset += (newX * oldXscale) - (newX * Xscale);
Yoffset += (newY * oldYscale) - (newY * Yscale);
Anything not declared is a "global"

Found out by searching the right words on google ...
This link explains the Affine Transformations http://gasi.ch/blog/zooming-in-flash-flex/
and so I can Tween the transformation Matrix:
var affineTransform:Matrix = board.transform.matrix;
affineTransform.translate( -mouseX, -mouseY );
affineTransform.scale( 0.8, 0.8 );
affineTransform.translate( mouseX, mouseY );
var originalMatrix:Matrix = board.transform.matrix;
TweenLite.to(originalMatrix, 0.7, {a:affineTransform.a, b:affineTransform.b, c:affineTransform.c, d:affineTransform.d, tx:affineTransform.tx, ty:affineTransform.ty, onUpdate:applyMatrix, onUpdateParams:[originalMatrix]});
:-)

private static function onMouseWheel(event:MouseEvent):void {
var zoomAmount:Number = 0.03;
if (event.delta < 0)
zoomAmount *= -1;
var x:int = largeLargeSprite.mouseX;
var y:int = largeLargeSprite.mouseY;
largeLargeSprite.scaleX += zoomAmount;
largeLargeSprite.scaleY += zoomAmount;
largeLargeSprite.x -= x * zoomAmount;
largeLargeSprite.y -= y * zoomAmount;
}

Related

As3 Trigonometry and math

i'm trying to create a minigame with circles rotating around circles
however, i have a problem when i shoot the circle and it hits the second circle it doesnt continue the angle but "jumping" to the other side i'm sure it something with the angle var that should reset or something. can you help me im getting nervous around here... :(
check the example
This is my code for the enter frame function that deals with the circles
public function UpdateCircles(e:Event):void
{
for (var i:int = 0; i < EnemySpriteVector.length; i++)
{
EnemySpriteVector[i].rotation += EnemySpriteVector[i].enemyspeed;
}
var rad:Number = angle * (Math.PI / 180); // Converting Degrees To Radians
if (IsplayerShoot)
{
playerSprite.x += Math.cos(rad) * PlayerCircleShootSpeed;
playerSprite.y += Math.sin(rad) * PlayerCircleShootSpeed;
for (var j:int = 0; j < EnemySpriteVector.length; j++)
{
if (EnemySpriteVector[j].hitTestPoint(playerSprite.x,playerSprite.y) && (EnemySpriteVector[j].IsCircleHit == false))
{
trace("hit");
EnemySpriteVector[j].IsCircleHit = true;
removeChild(EnemySpriteVector[0]);
EnemySpriteVector.splice(0, 1);
var EnemySprite:Sprite = new EnemySpriteClass();
EnemySpriteVector.push(EnemySprite);
addChild(EnemySprite);
EnemySprite.x = Math.random() * stage.stageWidth;
EnemySprite.y = Math.random() * stage.stageHeight;
IsplayerShoot = false;
}
}
}
else
{
playerSprite.x = EnemySpriteVector[0].x + EnemySpriteVector[0].radius * Math.cos(rad); // Position The Orbiter Along x-axis
playerSprite.y = EnemySpriteVector[0].y + EnemySpriteVector[0].radius * Math.sin(rad); // Position The Orbiter Along y-axis
angle += EnemySpriteVector[0].enemyspeed; // Object will orbit clockwise
playerSprite.rotation = (Math.atan2(playerSprite.y - EnemySpriteVector[0].y, playerSprite.x - EnemySpriteVector[0].x) * 180 / Math.PI); //only rotates the player circle itself
}
}
Looks like when the pink circle hits the green one it simply continues its rotation from where it left of. A quick solution would be to add 180 degrees to the angle. Keep in mind this will only work for static objects. If you want a more dynamic environment I would recommend using vectors (linear algebra). Vector math is really easy to understand and it hides a lot of complex trigonometry. You can start here :)

As3: Math, I'm too stupid for this. (trigonometry)

Okay, so I made some ai where a guard is following my character.
I am using this code:
private function getDegrees(radians:Number):Number
{
//return Math.floor(radians/(Math.PI/180));
return radians / 0.01745 | 0;
}
private function getRadians(delta_x:Number, delta_y:Number):Number
{
var r:Number = Math.atan2(delta_y, delta_x);
if (delta_y < 0)
{
//r += (2 * Math.PI);
r += 6.283;
}
return r;
}
And then in the loop
if(isShooting)
{
// calculate rotation based on mouse X & Y
_dx = this.x - _root.assassin.x;
_dy = this.y - _root.assassin.y;
// which way to rotate
_rotateTo = getDegrees(getRadians(_dx, _dy));
// keep rotation positive, between 0 and 360 degrees
if (_rotateTo > this.rotation + 180) _rotateTo -= 360;
if (_rotateTo < this.rotation - 180) _rotateTo += 360;
// ease rotation
_trueRotation = (_rotateTo - this.rotation) / _rotateSpeedMax;
// update rotation
this.rotation += _trueRotation;
gotoAndStop(5);
isWalking = false;
isStanding = false;
}
The thing is, the guard rotates weirdly, it's as if he isn't looking at the player. It's as if the player is somewhere else. Dunno, it just doesn't work.. I have no idea what is wrong with the code!
You should just compute the relative angle
_rotateTo = getDegrees(getRadians(_dx, _dy));
_trueRotation = (_rotateTo - this.rotation) / _rotateSpeedMax;
then normalize this to be as close to zero as possible
_trueRotation = (_trueRotation+900) %360 - 180;
and then cut this to the maximal rotation per step
_trueRotation = max(_trueRotation,_rotateSpeedMax* _timestep);
_trueRotation = min(_trueRotation,-_rotateSpeedMax*_timestep);
and use this then to update the forward direction
this.rotation += _trueRotation;
Speed usually is change per time, so there should be a multiplication with some time step for semantic consistency. Of course, you may have the time step equal to 1, or rotateSpeedMax is really a rotateMaxAngle (per step), then the multiplication with timestep can be removed.
To keep the rotation positive you should use modulus 360 like below for readability and (code) simplicity until and unless it's a clear performance issue: (I think this is right for AC3, just looked it up online quick)
_rotateTo = _rotateTo % 360;
The other possible issue is with how you define _dx and _dy. I'm guessing that the assassin is facing in the opposite direction as intended. The fix is to useI'm thinking it should either be:
_dx = _root.assassin.x - this.x;
_dy = _root.assassin.y - this.y;

AS3 Missile Logic

I want to create a simple missile object, which moves at a set speed and rotates towards a specific target at a given rotation speed. However, I'm having trouble figuring out the math of it. This is my code so far:
private function enterFrame(e:Event):void {
// Rotate the missile towards the target.
var targetAngle:Number = getAngle(target.x, target.y, x, y);
if (targetAngle < 0) {
targetAngle += 360;
}
if (targetAngle - turnSpeed > rotation) {
rotation += turnSpeed;
} else if (targetAngle + turnSpeed < rotation) {
rotation -= turnSpeed;
} else {
rotation = targetAngle;
}
// Set the target point to move to based on angle and speed.
var newX:Number = x + Math.sin(degreesToRadians(rotation)) * speed;
var newY:Number = y + Math.cos(degreesToRadians(rotation)) * speed;
// Move to new location
x = newX;
y = newY;
}
private function getAngle (x1:Number, y1:Number, x2:Number, y2:Number):Number {
var dx:Number = x2 - x1;
var dy:Number = y2 - y1;
return (Math.atan2(dy,dx) * 180) / Math.PI;
}
private function degreesToRadians(degrees:Number):Number {
return degrees * Math.PI / 180;
}
I've been trying to debug it using trace and such, but I can't seem to figure out where the problem is, most likely because there are many problems and I can't tell if I've fixed one because the others are masking it. I suspect that the issue(s) lie somewhere in the rotation calculations, since I'm pretty sure that the movement part is working as it should, but I can't say for sure.
At any rate, whatever I do, the missiles always seem to fly off in random directions, sometimes tracking towards straight up, or straight down, or just looping around after nothing in particular.

Draw random way in ActionScript

I'd like to move an AS 3 movieclip randomly. This is what I currently have, bound to the ENTER_FRAME event. This obviously moves the movieclip from the left upper to the right lower edge, so I need some kind of switch to add/substract the target positions.
function movePsycho(e:Event):void {
e.target.y += Math.random()*2;
e.target.x += Math.random()*2;
if (e.target.y >= stage.height || e.target.x >= stage.width)
e.target.removeEventListener(Event.ENTER_FRAME, movePsycho);
}
You don't need add/substract thing. You just have to make sure not only you get positive values out of your random, but negatives too, so it runs to all sides.
Try changing your random generating lines to this:
e.target.y += Math.random()*10 - 5;
e.target.x += Math.random()*10 - 5;
This will work if you want to make it move in a 5px radius.
I just realized you may want to generate a new random point on the screen, then move to that point and when your object reaches the destination generate another random point to go to. So if that's the case, try this:
mc.addEventListener(Event.ENTER_FRAME, onFrame);
var dirX:int = mc.x;
var dirY:int = mc.y;
function generateRandomPoint():void
{
dirX = Math.random() * stage.stageWidth;
dirY = Math.random() * stage.stageHeight;
}
function onFrame(e:Event):void
{
mc.x += (dirX - mc.x) * 0.1;
mc.y += (dirY - mc.y) * 0.1;
if(Math.abs(dirX - mc.x) < 1 || Math.abs(dirY - mc.y) < 1)
generateRandomPoint();
}
i don't know actionscript but you may find help with this
http://www.actionscript.org/forums/showthread.php3?t=270725

Optimizing my dynamic background engine for a 2d flash game in actionscript-3

Edit 2: judging on the lack of replies I start wondering if my issue is clear enough. Please tell me if I need to elaborate more.
Notice: see bottom for a code update!
Short introduction: I'm writing a 2 dimensional flash space game in actionscript. The universe is infinitely big, because of this feature, the background has to be rendered dynamically and background objects (gas clouds, stars, etc.) have to be positioned randomly.
I created a class called BackgroundEngine and it's working very well, the problem is however the rendering performance. This is how it works:
At startup, 4 background containers (each the size of the stage) are created around the player. Top left, top right, bottom left and bottom right. All background squares are added to a master container, for easy movement of the background. Now, there are 2 polling functions:
1) "garbage poller": looks for background containers that are 2 times the stage width or height away from the player's X or Y coord, respectively. If so, it will remove that background square and allow it for garbage collection.
2) "rendering poller": looks whether there is currently a background at all sides of the player (x - stageWidth, x + stageWidth, y - stageHeight, y + stageHeight). If not, it will draw a new background square at the corresponding location.
All background squares are created with the following function (the ones that are created dynamically and the four on startup):
<<< removed old code, see bottom for updated full source >>>
All the randoms you see there are making sure that the environment looks very unique on every square. This actually works great, the universe looks quite awesome.
The following assets are being used as background objects:
1) Simple stars : http://www.feedpostal.com/client/assets/background/1.png (you probably won't be able to see that one in a browser with a white background).
2) Bright stars : http://www.feedpostal.com/client/assets/background/2.png
3) White gas clouds : http://www.feedpostal.com/client/assets/background/3.png
4) Red gas clouds: http://www.feedpostal.com/client/assets/background/4.png
Important notes:
1) All assets are cached, so they don't have to be re-downloaded all the time. They are only downloaded once.
2) The images are not rotating or being scaled after they are created, so I enabled cacheAsBitmap for all objects, containers and the masterContainer.
3) I had to use PNG formats in Photoshop because GIFs did not seem to be rendered very well in flash when used with transparency.
So, the problem is that when I fly around the rendering of the background takes too much performance: the client starts "lagging" (FPS wise). Because of this, I need to optimize the background engine so that it will render much quicker. Can you folks help me out here?
Update 1:
This is what I have so far after the one response I got.
BackgroundEngine.as
package com.tommedema.background
{
import br.com.stimuli.loading.BulkLoader;
import com.tommedema.utils.Settings;
import com.tommedema.utils.UtilLib;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
public final class BackgroundEngine extends Sprite
{
private static var isLoaded:Boolean = false;
private static var bulkLoader:BulkLoader = BulkLoader.getLoader("main");
private static var masterContainer:Sprite;
private static var containers:Array = [];
private static var stageWidth:uint;
private static var stageHeight:uint;
private static var assets:Array;
//moves the background's X coord
public static function moveX(amount:Number):void
{
if (masterContainer)
{
masterContainer.x += amount;
collectGarbage();
drawNextContainer();
}
}
//moves the background's Y coord
public static function moveY(amount:Number):void
{
if (masterContainer)
{
masterContainer.y += amount;
collectGarbage();
drawNextContainer();
}
}
//returns whether the background engine has been loaded already
public static function loaded():Boolean
{
return isLoaded;
}
//loads the background engine
public final function load():void
{
//set stage width and height
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
//retreive all background assets
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/1.png", {id: "background/1.png"});
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/2.png", {id: "background/2.png"});
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/3.png", {id: "background/3.png"});
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/4.png", {id: "background/4.png"});
bulkLoader.addEventListener(BulkLoader.COMPLETE, assetsComplete);
bulkLoader.start();
//set isLoaded to true
isLoaded = true;
}
//poller function for drawing next background squares
private static function drawNextContainer():void
{
var stageCenterX:Number = stageWidth / 2;
var stageCenterY:Number = stageHeight / 2;
var curContainer:Bitmap = hasBackground(stageCenterX, stageCenterY);
if (curContainer)
{
//top left
if (!hasBackground(stageCenterX - stageWidth, stageCenterY - stageHeight)) drawNewSquare(curContainer.x - stageWidth, curContainer.y - stageHeight);
//top
if (!hasBackground(stageCenterX, stageCenterY - stageHeight)) drawNewSquare(curContainer.x, curContainer.y - stageHeight);
//top right
if (!hasBackground(stageCenterX + stageWidth, stageCenterY - stageHeight)) drawNewSquare(curContainer.x + stageWidth, curContainer.y - stageHeight);
//center left
if (!hasBackground(stageCenterX - stageWidth, stageCenterY)) drawNewSquare(curContainer.x - stageWidth, curContainer.y);
//center right
if (!hasBackground(stageCenterX + stageWidth, stageCenterY)) drawNewSquare(curContainer.x + stageWidth, curContainer.y);
//bottom left
if (!hasBackground(stageCenterX - stageWidth, stageCenterY + stageHeight)) drawNewSquare(curContainer.x - stageWidth, curContainer.y + stageHeight);
//bottom
if (!hasBackground(stageCenterX, stageCenterY + stageHeight)) drawNewSquare(curContainer.x, curContainer.y + stageHeight);
//bottom right
if (!hasBackground(stageCenterX + stageWidth, stageCenterY + stageHeight)) drawNewSquare(curContainer.x + stageWidth, curContainer.y + stageHeight);
}
}
//draws the next square and adds it to the master container
private static function drawNewSquare(x:Number, y:Number):void
{
containers.push(genSquareBg());
var cIndex:uint = containers.length - 1;
containers[cIndex].x = x;
containers[cIndex].y = y;
masterContainer.addChild(containers[cIndex]);
}
//returns whether the given location has a background and if so returns the corresponding square
private static function hasBackground(x:Number, y:Number):Bitmap
{
var stageX:Number;
var stageY:Number;
for(var i:uint = 0; i < containers.length; i++)
{
stageX = masterContainer.x + containers[i].x;
stageY = masterContainer.y + containers[i].y;
if ((containers[i]) && (stageX < x) && (stageX + stageWidth > x) && (stageY < y) && (stageY + stageHeight > y)) return containers[i];
}
return null;
}
//polling function for old background squares garbage collection
private static function collectGarbage():void
{
var stageX:Number;
var stageY:Number;
for(var i:uint = 0; i < containers.length; i++)
{
if (containers[i])
{
stageX = masterContainer.x + containers[i].x;
stageY = masterContainer.y + containers[i].y;
if ((stageX < -stageWidth * 1.5) || (stageX > stageWidth * 2.5) || (stageY < -stageHeight * 1.5) || (stageY > stageHeight * 2.5))
{
containers[i].parent.removeChild(containers[i]);
containers.splice(i, 1);
}
}
}
}
//dispatched when all assets have finished downloading
private final function assetsComplete(event:Event):void
{
assets = [];
assets.push(bulkLoader.getBitmap("background/1.png")); //star simple
assets.push(bulkLoader.getBitmap("background/2.png")); //star bright
assets.push(bulkLoader.getBitmap("background/3.png")); //cloud white
assets.push(bulkLoader.getBitmap("background/4.png")); //cloud red
init();
}
//initializes startup background containers
private final function init():void
{
masterContainer = new Sprite(); //create master container
//generate default background containers
containers.push(genSquareBg()); //top left
containers[0].x = 0;
containers[0].y = 0;
containers.push(genSquareBg()); //top
containers[1].x = stageWidth;
containers[1].y = 0;
containers.push(genSquareBg()); //top right
containers[2].x = stageWidth * 2;
containers[2].y = 0;
containers.push(genSquareBg()); //center left
containers[3].x = 0;
containers[3].y = stageHeight;
containers.push(genSquareBg()); //center
containers[4].x = stageWidth;
containers[4].y = stageHeight;
containers.push(genSquareBg()); //center right
containers[5].x = stageWidth * 2;
containers[5].y = stageHeight;
containers.push(genSquareBg()); //bottom left
containers[6].x = 0;
containers[6].y = stageHeight * 2;
containers.push(genSquareBg()); //bottom
containers[7].x = stageWidth;
containers[7].y = stageHeight * 2;
containers.push(genSquareBg()); //bottom right
containers[8].x = stageWidth * 2;
containers[8].y = stageHeight * 2;
//add the new containers to the master container
for (var i:uint = 0; i <= containers.length - 1; i++)
{
masterContainer.addChild(containers[i]);
}
//display the master container
masterContainer.x = 0 - stageWidth;
masterContainer.y = 0 - stageHeight;
masterContainer.cacheAsBitmap = true;
addChild(masterContainer);
}
//duplicates a bitmap display object
private static function dupeBitmap(source:Bitmap):Bitmap {
var data:BitmapData = source.bitmapData;
var bitmap:Bitmap = new Bitmap(data);
return bitmap;
}
//draws a simple star
private static function drawStar(x:Number, y:Number, width:uint, height:uint):Sprite
{
var creation:Sprite = new Sprite();
creation.graphics.lineStyle(1, 0xFFFFFF);
creation.graphics.beginFill(0xFFFFFF);
creation.graphics.drawRect(x, y, width, height);
return creation;
}
//generates a background square
private static function genSquareBg():Bitmap
{
//set 1% margin
var width:Number = stageWidth * 0.99;
var height:Number = stageHeight * 0.99;
var startX:Number = 0 + stageWidth / 100;
var startY:Number = 0 + stageHeight / 100;
var scale:Number;
var drawAmount:uint;
var tmpBitmap:Bitmap;
var tmpSprite:Sprite;
var i:uint;
//create container
var container:Sprite = new Sprite();
//draw simple stars
drawAmount = UtilLib.getRandomInt(100, 250);
for(i = 1; i <= drawAmount; i++)
{
tmpSprite = drawStar(0, 0, 1, 1);
tmpSprite.x = UtilLib.getRandomInt(0, stageWidth);
tmpSprite.y = UtilLib.getRandomInt(0, stageHeight);
tmpSprite.alpha = UtilLib.getRandomInt(3, 10) / 10;
scale = UtilLib.getRandomInt(2, 10) / 10;
tmpSprite.scaleX = tmpSprite.scaleY = scale;
container.addChild(tmpSprite);
}
//draw bright stars
if (Math.random() >= 0.8) drawAmount = UtilLib.getRandomInt(1, 2);
else drawAmount = 0;
for(i = 1; i <= drawAmount; i++)
{
tmpBitmap = dupeBitmap(assets[1]);
tmpBitmap.alpha = UtilLib.getRandomInt(3, 7) / 10;
tmpBitmap.rotation = UtilLib.getRandomInt(0, 360);
scale = UtilLib.getRandomInt(3, 10) / 10;
tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
tmpBitmap.x = UtilLib.getRandomInt(startX + tmpBitmap.width, width - tmpBitmap.width);
tmpBitmap.y = UtilLib.getRandomInt(startY + tmpBitmap.height, height - tmpBitmap.height);
container.addChild(tmpBitmap);
}
//draw white clouds
drawAmount = UtilLib.getRandomInt(1, 4);
for(i = 1; i <= drawAmount; i++)
{
tmpBitmap = dupeBitmap(assets[2]);
tmpBitmap.alpha = UtilLib.getRandomInt(1, 10) / 10;
scale = UtilLib.getRandomInt(15, 30);
tmpBitmap.scaleX = scale / 10;
tmpBitmap.scaleY = UtilLib.getRandomInt(scale / 2, scale) / 10;
tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
container.addChild(tmpBitmap);
}
//draw red clouds
drawAmount = UtilLib.getRandomInt(0, 1);
for(i = 1; i <= drawAmount; i++)
{
tmpBitmap = dupeBitmap(assets[3]);
tmpBitmap.alpha = UtilLib.getRandomInt(2, 6) / 10;
scale = UtilLib.getRandomInt(5, 30) / 10;
tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
container.addChild(tmpBitmap);
}
//convert all layers to a single bitmap layer and return
var bitmapData:BitmapData = new BitmapData(stageWidth, stageHeight, true, 0x000000);
bitmapData.draw(container);
container = null;
var bitmapContainer:Bitmap = new Bitmap(bitmapData);
bitmapContainer.cacheAsBitmap = true;
return bitmapContainer;
}
}
}
When the player is moving, the background moveX and moveY methods are called with the inverse direction of the player. This will also cause the collectGarbage and drawNextContainer methods to be called.
The problem with this setup is that there are a minimum of 9 containers active at all times. Top left, top, top right, center left, center, center right, bottom left, bottom and bottom right. This takes a lot of performance.
Edit: I also wonder, should I use cacheAsBitmap? If so, on which images? On the containers and the master container or on only one of them? When I enable it for all images (even the temporary sprite objects) it's actually lagging more.
Update 2:
This version is using squares that are twice as big as the stage. Only one or two squares should be loaded at a time. It is better, but I still notice a performance hit while moving. It makes the client freeze for a very brief moment. Any idea how to optimize it?
BackgroundEngine2.as
package com.tommedema.background
{
import br.com.stimuli.loading.BulkLoader;
import com.tommedema.utils.Settings;
import com.tommedema.utils.UtilLib;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
public final class BackgroundEngine2 extends Sprite
{
//general
private static var isLoaded:Boolean = false;
private static var bulkLoader:BulkLoader = BulkLoader.getLoader("main");
private static var assets:Array;
//objects
private static var masterContainer:Sprite;
private static var containers:Array = [];
//stage
private static var stageWidth:uint;
private static var stageHeight:uint;
private static var stageCenterX:Number;
private static var stageCenterY:Number;
//moves the background's X coord
public static function moveX(amount:Number):void
{
if (!masterContainer) return;
masterContainer.x += amount;
collectGarbage();
drawNextContainer();
}
//moves the background's Y coord
public static function moveY(amount:Number):void
{
if (!masterContainer) return;
masterContainer.y += amount;
collectGarbage();
drawNextContainer();
}
//returns whether the background engine has been loaded already
public static function loaded():Boolean
{
return isLoaded;
}
//loads the background engine
public final function load():void
{
//set stage width, height and center
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
stageCenterX = stageWidth / 2;
stageCenterY = stageHeight / 2;
//retreive background assets
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/1.png", {id: "background/1.png"});
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/2.png", {id: "background/2.png"});
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/3.png", {id: "background/3.png"});
bulkLoader.add(Settings.ASSETS_PRE_URL + "background/4.png", {id: "background/4.png"});
bulkLoader.addEventListener(BulkLoader.COMPLETE, assetsComplete);
bulkLoader.start();
//set isLoaded to true
isLoaded = true;
}
//poller function for drawing next background squares
private static function drawNextContainer():void
{
var curContainer:Bitmap = hasBackground(stageCenterX, stageCenterY);
if (curContainer)
{
if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top left
drawNewSquare(curContainer.x - curContainer.width, curContainer.y - curContainer.height);
if (!hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75)) //top
drawNewSquare(curContainer.x, curContainer.y - curContainer.height);
if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75)) //top right
drawNewSquare(curContainer.x + curContainer.width, curContainer.y - curContainer.height);
if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY)) //center left
drawNewSquare(curContainer.x - curContainer.width, curContainer.y);
if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY)) //center right
drawNewSquare(curContainer.x + curContainer.width, curContainer.y);
if (!hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom left
drawNewSquare(curContainer.x - curContainer.width, curContainer.y + curContainer.height);
if (!hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75)) //bottom center
drawNewSquare(curContainer.x, curContainer.y + curContainer.height);
if (!hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75)) //bottom right
drawNewSquare(curContainer.x + curContainer.width, curContainer.y + curContainer.height);
}
}
//draws the next square and adds it to the master container
private static function drawNewSquare(x:Number, y:Number):void
{
containers.push(genSquareBg());
var cIndex:uint = containers.length - 1;
containers[cIndex].x = x;
containers[cIndex].y = y;
masterContainer.addChild(containers[cIndex]);
}
//returns whether the given location has a background and if so returns the corresponding square
private static function hasBackground(x:Number, y:Number):Bitmap
{
var stageX:Number;
var stageY:Number;
for(var i:uint = 0; i < containers.length; i++)
{
stageX = masterContainer.x + containers[i].x;
stageY = masterContainer.y + containers[i].y;
if ((containers[i]) && (stageX < x) && (stageX + containers[i].width > x) && (stageY < y) && (stageY + containers[i].height > y)) return containers[i];
}
return null;
}
//polling function for old background squares garbage collection
private static function collectGarbage():void
{
var stageX:Number;
var stageY:Number;
for(var i:uint = 0; i < containers.length; i++)
{
if ((containers[i]) && (!isRequiredContainer(containers[i])))
{
masterContainer.removeChild(containers[i]);
containers.splice(i, 1);
}
}
}
//returns whether the given container is required for display
private static function isRequiredContainer(container:Bitmap):Boolean
{
if (hasBackground(stageCenterX, stageCenterY) == container) //center
return true;
if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top left
return true;
if (hasBackground(stageCenterX, stageCenterY - stageHeight * 0.75) == container) //top
return true;
if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY - stageHeight * 0.75) == container) //top right
return true;
if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY) == container) //center left
return true;
if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY) == container) //center right
return true;
if (hasBackground(stageCenterX - stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom left
return true;
if (hasBackground(stageCenterX, stageCenterY + stageHeight * 0.75) == container) //bottom center
return true;
if (hasBackground(stageCenterX + stageWidth * 0.75, stageCenterY + stageHeight * 0.75) == container) //bottom right
return true;
return false;
}
//dispatched when all assets have finished downloading
private final function assetsComplete(event:Event):void
{
assets = [];
assets.push(bulkLoader.getBitmap("background/1.png")); //star simple
assets.push(bulkLoader.getBitmap("background/2.png")); //star bright
assets.push(bulkLoader.getBitmap("background/3.png")); //cloud white
assets.push(bulkLoader.getBitmap("background/4.png")); //cloud red
init();
}
//initializes startup background containers
private final function init():void
{
masterContainer = new Sprite(); //create master container
//generate default background container
containers.push(genSquareBg()); //top left
containers[0].x = 0;
containers[0].y = 0;
masterContainer.addChild(containers[0]);
//display the master container
masterContainer.x = -(stageWidth / 2);
masterContainer.y = -(stageHeight / 2);
masterContainer.cacheAsBitmap = true;
addChild(masterContainer);
}
//duplicates a bitmap display object
private static function dupeBitmap(source:Bitmap):Bitmap {
var data:BitmapData = source.bitmapData;
var bitmap:Bitmap = new Bitmap(data);
return bitmap;
}
//draws a simple star
private static function drawStar(x:Number, y:Number, width:uint, height:uint):Sprite
{
var creation:Sprite = new Sprite();
creation.graphics.lineStyle(1, 0xFFFFFF);
creation.graphics.beginFill(0xFFFFFF);
creation.graphics.drawRect(x, y, width, height);
return creation;
}
//generates a background square
private static function genSquareBg():Bitmap
{
var width:Number = stageWidth * 2;
var height:Number = stageHeight * 2;
var startX:Number = 0;
var startY:Number = 0;
var scale:Number;
var drawAmount:uint;
var tmpBitmap:Bitmap;
var tmpSprite:Sprite;
var i:uint;
//create container
var container:Sprite = new Sprite();
//draw simple stars
drawAmount = UtilLib.getRandomInt(100, 250);
for(i = 1; i <= drawAmount; i++)
{
tmpSprite = drawStar(0, 0, 1, 1);
tmpSprite.x = UtilLib.getRandomInt(startX, width);
tmpSprite.y = UtilLib.getRandomInt(startY, height);
tmpSprite.alpha = UtilLib.getRandomInt(3, 10) / 10;
scale = UtilLib.getRandomInt(5, 15) / 10;
tmpSprite.scaleX = tmpSprite.scaleY = scale;
container.addChild(tmpSprite);
}
//draw bright stars
drawAmount = UtilLib.getRandomInt(1, 2);
for(i = 1; i <= drawAmount; i++)
{
tmpBitmap = dupeBitmap(assets[1]);
tmpBitmap.alpha = UtilLib.getRandomInt(3, 7) / 10;
tmpBitmap.rotation = UtilLib.getRandomInt(0, 360);
scale = UtilLib.getRandomInt(3, 10) / 10;
tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
tmpBitmap.x = UtilLib.getRandomInt(startX + tmpBitmap.width, width - tmpBitmap.width);
tmpBitmap.y = UtilLib.getRandomInt(startY + tmpBitmap.height, height - tmpBitmap.height);
container.addChild(tmpBitmap);
}
//draw white clouds
drawAmount = UtilLib.getRandomInt(2, 4);
for(i = 1; i <= drawAmount; i++)
{
tmpBitmap = dupeBitmap(assets[2]);
tmpBitmap.alpha = UtilLib.getRandomInt(1, 10) / 10;
scale = UtilLib.getRandomInt(15, 40);
tmpBitmap.scaleX = scale / 10;
tmpBitmap.scaleY = UtilLib.getRandomInt(scale / 2, scale * 2) / 10;
tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
container.addChild(tmpBitmap);
}
//draw red clouds
drawAmount = UtilLib.getRandomInt(0, 2);
for(i = 1; i <= drawAmount; i++)
{
tmpBitmap = dupeBitmap(assets[3]);
tmpBitmap.alpha = UtilLib.getRandomInt(2, 6) / 10;
scale = UtilLib.getRandomInt(5, 40) / 10;
tmpBitmap.scaleX = scale; tmpBitmap.scaleY = scale;
tmpBitmap.x = UtilLib.getRandomInt(startX, width - tmpBitmap.width);
tmpBitmap.y = UtilLib.getRandomInt(startY, height - tmpBitmap.height);
container.addChild(tmpBitmap);
}
//convert all layers to a single bitmap layer and return
var bitmapData:BitmapData = new BitmapData(width, height, true, 0x000000);
bitmapData.draw(container);
container = null;
var bitmapContainer:Bitmap = new Bitmap(bitmapData);
//bitmapContainer.cacheAsBitmap = true;
return bitmapContainer;
}
}
}
ok, this should show you can really get another category of numbers with other aproaches ...
the limit here is not the number of stars, the limit is density, i.e. the number of stars visible at the same time ... with text disabled, i can get up to 700 # 30fps, on a Core2Duo, with quite a recent version of the debug player ...
i realized, flash player is not very good at clipping ... and that actually, using the most simple way, you spend a whole lot of time moving around objects, that are far from being visible ...
to really be able to optimize things, i chose to use MVC here ... not in the classic bloated way ... the idea is to handle the model, and if any elements are visible, create views for them ...
now the best aproach is to build up a spatial tree ...
you have leaves, containing objects, and nodes containing leaves or nodes
if you add an object to a leaf and it surpases a certain size, you turn it into a node with nxn leaves, redestributing its children between
any object added to the background will be added to a grid, determined by the object's coordinates ... grids are created just-in-time, an start off as leaves
the big advantage of this is, that you can quickly isolate the visible nodes/leaves.
in each iteration, only the nodes/leaves which either turn visible, or are already visible (and may become invisible), are interesting. you need not do any updates in the rest of the tree. after finding all the visible objects, you create views for objects that turn visible, update the position of those that simply stay visible, and destroy views for objects that become invisible ...
this saves an awful lot of everything ... memory and computation power ...
if you try with a huge world size (100000), you will see, that you run out of RAM quickly, long before CPU does anything ... instantiating 500000 stars uses 700MB ram, with about 50 stars visible, running at 70 fps without any tremendous CPU usage ...
the engine is just a proof of concept ... and code looks awful ... the only feature i am currently proud about is, that it supports object to occupate a certain area, which is why an object can be part of several leafs ... i think, this is something i will remove though, because it should give me a great speed up ... you also see, it stalls a little, while adding stars, which happens, when leafs flip to nodes ...
i am quite sure, this is not the best way for a spatial subdivision tree, it's just that everything i found seemed kind of useless to me ... probably someone who studied (and understood) CS, can refine my approach ... :)
other than that, i used an object pool for the views, and Haxe, since it performs better ... a thing that probably is not so clever, is to move all the views individually, instead of putting them on one layer and moving that around ...
some people also render things into BitmapDatas manually, to gain performance, which seems to work quite well (just can't find the question right now) ... you should however consider using copyPixels instead of draw ...
hope this helps ... ;)
edit: i decided to turn my detailed answer into a blog post ... have fun reading ... ;)
You may want to see if you can blit all of the pieces together into a flattend Bitmaps as you go. Draw all of the layers and then use BitmapData's draw method to combine them into a single Bitmap.
Even with cacheAsBitmap on for all of the pieces Flash is still having to combine all of those pieces every frame.
Try stretching the window of the player, both bigger and smaller. If that has a significant effect on frame rate, your fastest and easiest way to improve performance is to shrink the size of the stage. This tends to be an unpopular answer when presented to people - especially artists - but if your bottleneck is in the size of your stage, there is not much you can do in code to fix that.
What if instead of destroying background squares you just put them in a pile of "ready to go" squares that you can draw on, capping it at like 4? then you don't have to create one when you need one you just move it into the right spot and maybe shuffle the stars or something.
[would add example but i don't write AS3 code :(]
You should be able to simplify some of your math by using stored variables instead of stageCenterX + stageWidth * 0.75, and similar since they don't change.
Have you considered using
HitTestPoint instead of doing the math to check positions of containers? It's a native function, so it might be faster.
You should use a Shape instead of a Sprite if you don't need to add children to the object. e.g., your star. This might help quite a bit.
What if you created a set of star backgrounds at the start of the program. Then converted them to bitmaps, and saved them for later reuse. e.g., create a star background, convert it to bitmap data, and save this in an array. Do this, say, 10 times, and then when you need a background to just randomly select one, and apply your other shapes to it. The benefit of doing this is that you don't have to render 100-250 Sprites or Shapes each time you create a new background--that takes time.
EDIT: New idea:
Maybe you can play with the idea of only drawing the stars on the backgrounds rather than adding individual objects. The number of objects added to the screen are a big part of the problem. So I'm suggesting you draw the stars on the container directly, but with different sizes and alphas. Then scale the container down so that you get the effect you're looking for. You could reduce the display footprint from 500-1000 stars down to 0. That would be a huge improvement if you can get the effect you need from it.
Try using a single large BitmapData with it's own Bitmap, larger than the stage (although you might hit the limits of BitmapDatas if you have a really big stage and/or are using flash 9), and drawing new background images to it using the copyPixels method (a really fast way of copying pixels, faster than draw(), at least as far as I've seen).
Pan the large Bitmap around when you want and when you reach an edge, pan the bitmap to the other side, copyPixels the whole thing back to where it was previously (so the image should stay in the same place relative to the stage, but the Bitmap should move) and copyPixels new images where they are missing.
Since you are using alpha for the images as well, so you might want to check all the parameters of copyPixels if it doesn't copy alpha as you wanted it to (probably mergeAlpha?).
So, to recap, use a single large Bitmap that extends well over the boundaries of the stage, have the images ready as BitmapDatas, do the wrap trick and fill in the blanks with copyPixels from images.
I don't know if this way would perform better (the copyPixels over the whole bitmap worries me a little), but it's definitely something to try. Good luck :)