Bink Here's the full source code AI wrote for me. Would this represent a typical way of using Starling + Juggler? Anything stand out that could be optimized? Thank you.
Main.as
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import starling.core.Starling;
[SWF(width="1920", height="1080", frameRate="60", backgroundColor="#000000")]
public class Main extends Sprite
{
private var _starling:Starling;
public function Main()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_starling = new Starling(BenchmarkApp, stage);
_starling.antiAliasing = 1;
_starling.showStats = true;
_starling.start();
}
}
}
//
// -------------------------------------------------------------------------------------------------------
//
BenchmarkApp.as
package
{
import flash.display.Bitmap;
import starling.core.Starling;
import starling.display.Sprite;
import starling.display.Image;
import starling.events.Event;
import starling.events.EnterFrameEvent;
import starling.events.KeyboardEvent;
import starling.text.TextField;
import starling.text.TextFormat;
import starling.textures.Texture;
import starling.textures.TextureAtlas;
public class BenchmarkApp extends Sprite
{
// ── Embedded texture sheet ──────────────────────────────────────────
[Embed(source="../assets/sheet.png")]
private static const SheetBitmap:Class;
// ── Atlas layout ────────────────────────────────────────────────────
private static const BG_X:Number = 0;
private static const BG_Y:Number = 0;
private static const BG_W:Number = 1920;
private static const BG_H:Number = 1080;
private static const STRIP_X:Number = 0;
private static const STRIP_Y:Number = 1086;
private static const FRAME_W:Number = 100;
private static const FRAME_H:Number = 100;
private static const FRAME_COUNT:int = 10;
// ── Screen / spawn ──────────────────────────────────────────────────
private static const SW:Number = 1920;
private static const SH:Number = 1080;
private static const SPAWN_X:Number = 960;
private static const SPAWN_Y:Number = 540;
private static const SPRITES_PER_KEY:int = 100;
// ── Speed / anim ranges ─────────────────────────────────────────────
private static const SPEED_MIN:Number = 60;
private static const SPEED_MAX:Number = 340;
private static const ANIM_FPS_MIN:Number = 6;
private static const ANIM_FPS_MAX:Number = 24;
// ── HUD throttle — update text every N frames ───────────────────────
private static const HUD_UPDATE_INTERVAL:int = 10;
// ── Object pool ─────────────────────────────────────────────────────
private static const POOL_PREALLOC:int = 500; // pre-warm on startup
// ── State ───────────────────────────────────────────────────────────
private var _frameTextures:Vector.<Texture>;
private var _spriteLayer:Sprite;
private var _benchSprites:Vector.<BenchSprite> = new Vector.<BenchSprite>();
// Pool of inactive BenchSprite objects ready for reuse
private var _pool:Vector.<BenchSprite> = new Vector.<BenchSprite>();
private var _hud:TextField;
// FPS tracking
private var _fpsFrames:int = 0;
private var _fpsAccum:Number = 0;
private var _fps:Number = 0;
// HUD throttle counter
private var _hudTick:int = 0;
// ───────────────────────────────────────────────────────────────────
public function BenchmarkApp()
{
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
private function onAddedToStage(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
buildAtlas();
buildBackground();
buildSpriteLayer();
buildHUD();
prewarmPool();
addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
// ── Build atlas (single texture upload) ─────────────────────────────
private function buildAtlas():void
{
var bmp:Bitmap = new SheetBitmap() as Bitmap;
var tex:Texture = Texture.fromBitmapData(bmp.bitmapData, false);
var xml:XML = <TextureAtlas imagePath="sheet.png"/>;
xml.appendChild(
<SubTexture name="bg"
x={BG_X} y={BG_Y} width={BG_W} height={BG_H}/>
);
for (var i:int = 0; i < FRAME_COUNT; i++)
{
xml.appendChild(
<SubTexture name={"frame_" + i}
x={STRIP_X + i * FRAME_W} y={STRIP_Y}
width={FRAME_W} height={FRAME_H}/>
);
}
var atlas:TextureAtlas = new TextureAtlas(tex, xml);
_frameTextures = new Vector.<Texture>(FRAME_COUNT, true);
for (i = 0; i < FRAME_COUNT; i++)
_frameTextures[i] = atlas.getTexture("frame_" + i);
}
private function buildBackground():void
{
var bmp:Bitmap = new SheetBitmap() as Bitmap;
var tex:Texture = Texture.fromBitmapData(bmp.bitmapData, false);
var xml:XML = <TextureAtlas imagePath="sheet.png">
<SubTexture name="bg" x="0" y="0" width="1920" height="1080"/>
</TextureAtlas>;
var atlas:TextureAtlas = new TextureAtlas(tex, xml);
var bg:Image = new Image(atlas.getTexture("bg"));
bg.x = 0;
bg.y = 0;
addChild(bg);
}
private function buildSpriteLayer():void
{
_spriteLayer = new Sprite();
addChild(_spriteLayer);
}
private function buildHUD():void
{
var fmt:TextFormat = new TextFormat("_sans", 28, 0xFFFF00);
_hud = new TextField(500, 70, "", fmt);
_hud.x = 10;
_hud.y = 10;
addChild(_hud);
updateHUD();
}
private function updateHUD():void
{
_hud.text = "FPS: " + _fps.toFixed(1) +
" Sprites: " + _benchSprites.length;
}
// ── Pre-allocate pool objects to avoid GC spikes during benchmark ───
private function prewarmPool():void
{
for (var i:int = 0; i < POOL_PREALLOC; i++)
_pool.push(createBenchSprite());
}
// ── Allocate a fresh BenchSprite (no display list / juggler yet) ────
private function createBenchSprite():BenchSprite
{
return new BenchSprite(
_frameTextures,
SPAWN_X, SPAWN_Y,
SW, SH,
SPEED_MIN, SPEED_MAX,
ANIM_FPS_MIN, ANIM_FPS_MAX
);
}
// ── Get from pool or allocate if pool is empty ───────────────────────
private function acquireSprite():BenchSprite
{
var bs:BenchSprite;
if (_pool.length > 0)
{
bs = _pool.pop();
bs.reset(SPAWN_X, SPAWN_Y, SPEED_MIN, SPEED_MAX, ANIM_FPS_MIN, ANIM_FPS_MAX);
}
else
{
bs = createBenchSprite();
}
return bs;
}
private function spawnBatch():void
{
for (var i:int = 0; i < SPRITES_PER_KEY; i++)
{
var bs:BenchSprite = acquireSprite();
_spriteLayer.addChild(bs.clip);
Starling.juggler.add(bs.clip);
_benchSprites.push(bs);
}
}
private function onEnterFrame(e:EnterFrameEvent):void
{
var dt:Number = e.passedTime;
// FPS
_fpsAccum += dt;
_fpsFrames++;
if (_fpsAccum >= 0.25)
{
_fps = _fpsFrames / _fpsAccum;
_fpsAccum = 0;
_fpsFrames = 0;
}
// Move sprites
var len:int = _benchSprites.length;
for (var i:int = 0; i < len; i++)
_benchSprites[i].update(dt);
// Throttle HUD — only rebuild the text every 10 frames
if (++_hudTick >= HUD_UPDATE_INTERVAL)
{
_hudTick = 0;
updateHUD();
}
}
private function onKeyDown(e:KeyboardEvent):void
{
spawnBatch();
}
}
}
//
// -------------------------------------------------------------------------------------------------------
//
BenchSprite.as
package
{
import starling.display.MovieClip;
import starling.textures.Texture;
/**
* BenchSprite wraps a MovieClip and handles movement/bouncing.
* Supports reset() so instances can be recycled from an object pool
* without triggering garbage collection.
*/
public class BenchSprite
{
public var clip:MovieClip;
// Physics
private var _vx:Number;
private var _vy:Number;
private var _sw:Number;
private var _sh:Number;
private var _hw:Number; // half-width (constant — sprites never scale)
private var _hh:Number; // half-height
// Keep a ref to frames so reset() can rebuild the MovieClip fps
private var _frames:Vector.<Texture>;
// ────────────────────────────────────────────────────────────────────
public function BenchSprite(
frames:Vector.<Texture>,
spawnX:Number, spawnY:Number,
screenW:Number, screenH:Number,
speedMin:Number, speedMax:Number,
animFpsMin:Number, animFpsMax:Number
) {
_frames = frames;
_sw = screenW;
_sh = screenH;
// Build the MovieClip once — reset() reuses it
var animFps:Number = animFpsMin + Math.random() * (animFpsMax - animFpsMin);
clip = new MovieClip(frames, animFps);
clip.loop = true;
clip.pivotX = clip.width * 0.5;
clip.pivotY = clip.height * 0.5;
_hw = clip.width * 0.5;
_hh = clip.height * 0.5;
applyRandomState(spawnX, spawnY, speedMin, speedMax, animFpsMin, animFpsMax);
}
// ── Called by the pool instead of constructing a new object ─────────
public function reset(
spawnX:Number, spawnY:Number,
speedMin:Number, speedMax:Number,
animFpsMin:Number, animFpsMax:Number
):void {
applyRandomState(spawnX, spawnY, speedMin, speedMax, animFpsMin, animFpsMax);
}
// ── Shared init logic used by constructor and reset ──────────────────
private function applyRandomState(
spawnX:Number, spawnY:Number,
speedMin:Number, speedMax:Number,
animFpsMin:Number, animFpsMax:Number
):void {
clip.x = spawnX;
clip.y = spawnY;
// Random velocity
var angle:Number = Math.random() * Math.PI * 2;
var speed:Number = speedMin + Math.random() * (speedMax - speedMin);
_vx = Math.cos(angle) * speed;
_vy = Math.sin(angle) * speed;
// Random animation fps
var animFps:Number = animFpsMin + Math.random() * (animFpsMax - animFpsMin);
clip.fps = animFps;
// Random starting frame
clip.currentFrame = int(Math.random() * _frames.length);
}
// ── Movement + bounce only — animation driven by Juggler ────────────
public function update(dt:Number):void
{
clip.x += _vx * dt;
clip.y += _vy * dt;
if (clip.x - _hw < 0) { clip.x = _hw; _vx = Math.abs(_vx); }
else if (clip.x + _hw > _sw) { clip.x = _sw - _hw; _vx = -Math.abs(_vx); }
if (clip.y - _hh < 0) { clip.y = _hh; _vy = Math.abs(_vy); }
else if (clip.y + _hh > _sh) { clip.y = _sh - _hh; _vy = -Math.abs(_vy); }
}
}
}