diff --git a/flixel/FlxBasic.hx b/flixel/FlxBasic.hx index f3dd46dcec..5c0b0be2ef 100644 --- a/flixel/FlxBasic.hx +++ b/flixel/FlxBasic.hx @@ -1,7 +1,5 @@ package flixel; -import flixel.FlxG; -import flixel.group.FlxTypedGroup; import flixel.util.FlxDestroyUtil.IFlxDestroyable; import flixel.util.FlxStringUtil; diff --git a/flixel/FlxObject.hx b/flixel/FlxObject.hx index e9f449d55e..117afe9018 100644 --- a/flixel/FlxObject.hx +++ b/flixel/FlxObject.hx @@ -4,7 +4,6 @@ import flash.display.Graphics; import flixel.FlxBasic; import flixel.group.FlxGroup; import flixel.group.FlxSpriteGroup; -import flixel.group.FlxTypedGroup; import flixel.tile.FlxTilemap; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; diff --git a/flixel/effects/particles/FlxEmitter.hx b/flixel/effects/particles/FlxEmitter.hx index cd7fec790a..296c70f0b5 100644 --- a/flixel/effects/particles/FlxEmitter.hx +++ b/flixel/effects/particles/FlxEmitter.hx @@ -1,3 +1,899 @@ package flixel.effects.particles; -typedef FlxEmitter = FlxTypedEmitter; \ No newline at end of file +import flash.display.BlendMode; +import flixel.effects.particles.FlxParticle; +import flixel.FlxG; +import flixel.FlxObject; +import flixel.FlxSprite; +import flixel.group.FlxGroup.FlxTypedGroup; +import flixel.math.FlxPoint; +import flixel.math.FlxRandom; +import flixel.system.FlxAssets.FlxGraphicAsset; +import flixel.util.FlxDestroyUtil; +import flixel.util.FlxStringUtil; + +typedef FlxEmitter = FlxTypedEmitter; + +/** + * FlxTypedEmitter is a lightweight particle emitter. + * It can be used for one-time explosions or for + * continuous fx like rain and fire. FlxEmitter + * is not optimized or anything; all it does is launch + * FlxParticle objects out at set intervals + * by setting their positions and velocities accordingly. + * It is easy to use and relatively efficient, + * relying on FlxGroup's RECYCLE POWERS. + */ +class FlxTypedEmitter extends FlxTypedGroup +{ + /** + * Set your own particle class type here. The custom class must extend FlxParticle. + * Default is FlxParticle. + */ + public var particleClass:Class; + /** + * Determines whether the emitter is currently emitting particles. + * It is totally safe to directly toggle this. + */ + public var emitting:Bool = false; + /** + * How often a particle is emitted (if emitter is started with Explode == false). + */ + public var frequency:Float = 0.1; + /** + * Sets particle's blend mode. null by default. + * Warning: expensive on flash target + */ + public var blend:BlendMode; + /** + * How much each particle should bounce. 1 = full bounce, 0 = no bounce. + */ + public var bounce:Float = 0; + + /** + * The width of the emitter. Particles can be randomly generated from anywhere within this box. + */ + public var width(get, set):Float; + /** + * The height of the emitter. Particles can be randomly generated from anywhere within this box. + */ + public var height(get, set):Float; + /** + * The x position of this emitter. + */ + public var x(get, set):Float; + /** + * The y position of this emitter. + */ + public var y(get, set):Float; + /** + * Sets the acceleration.y member of each particle to this value on launch. + */ + public var gravity(get, set):Float; + /** + * The minimum possible angular velocity of a particle. The default value is -360. + * NOTE: rotating particles are more expensive to draw than non-rotating ones! + */ + public var minRotation(get, set):Float; + /** + * The maximum possible angular velocity of a particle. The default value is 360. + * NOTE: rotating particles are more expensive to draw than non-rotating ones! + */ + public var maxRotation(get, set):Float; + /** + * How long each particle lives once it is emitted. + * Set lifespan to 'zero' for particles to live forever. + */ + public var lifespan(get, set):Float; + /** + * The x position range of the emitter in world space. + */ + public var xPosition(default, null):Bounds; + /** + * The y position range of emitter in world space. + */ + public var yPosition(default, null):Bounds; + /** + * The x velocity range of a particle. + * The default value is (-100,-100). + */ + public var xVelocity(default, null):Bounds; + /** + * The y velocity range of a particle. + * The default value is (100,100). + */ + public var yVelocity(default, null):Bounds; + /** + * The minimum and maximum possible angular velocity of a particle. The default value is (-360, 360). + * NOTE: rotating particles are more expensive to draw than non-rotating ones! + */ + public var rotation(default, null):Bounds; + public var life(default, null):Bounds; + /** + * Sets start scale range (when particle emits) + */ + public var startScale(default, null):Bounds; + /** + * Sets end scale range (when particle dies) + */ + public var endScale(default, null):Bounds; + /** + * Sets start alpha range (when particle emits) + */ + public var startAlpha(default, null):Bounds; + /** + * Sets end alpha range (when particle emits) + */ + public var endAlpha(default, null):Bounds; + /** + * Sets start red color component range (when particle emits) + */ + public var startRed(default, null):Bounds; + /** + * Sets start green color component range (when particle emits) + */ + public var startGreen(default, null):Bounds; + /** + * Sets start blue color component range (when particle emits) + */ + public var startBlue(default, null):Bounds; + /** + * Sets end red color component range (when particle emits) + */ + public var endRed(default, null):Bounds; + /** + * Sets end green color component range (when particle emits) + */ + public var endGreen(default, null):Bounds; + /** + * Sets end blue color component range (when particle emits) + */ + public var endBlue(default, null):Bounds; + /** + * The X and Y drag component of particles launched from the emitter. + */ + public var particleDrag(default, null):FlxPoint; + /** + * Sets the acceleration member of each particle to this value on launch. + */ + public var acceleration(default, null):FlxPoint; + + /** + * Internal helper for deciding how many particles to launch. + */ + private var _quantity:Int = 0; + /** + * Internal helper for the style of particle emission (all at once, or one at a time). + */ + private var _explode:Bool = true; + /** + * Internal helper for deciding when to launch particles or kill them. + */ + private var _timer:Float = 0; + /** + * Internal counter for figuring out how many particles to launch. + */ + private var _counter:Int = 0; + /** + * Internal point object, handy for reusing for memory mgmt purposes. + */ + private var _point:FlxPoint; + /** + * Internal helper for automatic call the kill() method + */ + private var _waitForKill:Bool = false; + + /** + * Creates a new FlxTypedEmitter object at a specific position. + * Does NOT automatically generate or attach particles! + * + * @param X The X position of the emitter. + * @param Y The Y position of the emitter. + * @param Size Optional, specifies a maximum capacity for this emitter. + */ + public function new(X:Float = 0, Y:Float = 0, Size:Int = 0) + { + super(Size); + + xPosition = new Bounds(X, 0); + yPosition = new Bounds(Y, 0); + xVelocity = new Bounds( -100, 100); + yVelocity = new Bounds( -100, 100); + rotation = new Bounds( -360, 360); + startScale = new Bounds(1, 1); + endScale = new Bounds(1, 1); + startAlpha = new Bounds(1.0, 1.0); + endAlpha = new Bounds(1.0, 1.0); + startRed = new Bounds(1.0, 1.0); + startGreen = new Bounds(1.0, 1.0); + startBlue = new Bounds(1.0, 1.0); + endRed = new Bounds(1.0, 1.0); + endGreen = new Bounds(1.0, 1.0); + endBlue = new Bounds(1.0, 1.0); + + acceleration = FlxPoint.get(0, 0); + particleClass = cast FlxParticle; + particleDrag = FlxPoint.get(); + + life = new Bounds(3, 3); + exists = false; + _point = FlxPoint.get(); + } + + /** + * Clean up memory. + */ + override public function destroy():Void + { + _point = FlxDestroyUtil.put(_point); + acceleration = FlxDestroyUtil.put(acceleration); + particleDrag = FlxDestroyUtil.put(particleDrag); + + xPosition = null; + yPosition = null; + xVelocity = null; + yVelocity = null; + rotation = null; + startScale = null; + endScale = null; + startAlpha = null; + endAlpha = null; + startRed = null; + startGreen = null; + startBlue = null; + endRed = null; + endGreen = null; + endBlue = null; + blend = null; + _point = null; + + super.destroy(); + } + + /** + * This function generates a new array of particle sprites to attach to the emitter. + * + * @param Graphics If you opted to not pre-configure an array of FlxParticle objects, you can simply pass in a particle image or sprite sheet. + * @param Quantity The number of particles to generate when using the "create from image" option. + * @param BakedRotations How many frames of baked rotation to use (boosts performance). Set to zero to not use baked rotations. + * @param Multiple Whether the image in the Graphics param is a single particle or a bunch of particles (if it's a bunch, they need to be square!). + * @param Collide Whether the particles should be flagged as not 'dead' (non-colliding particles are higher performance). 0 means no collisions, 0-1 controls scale of particle's bounding box. + * @param AutoBuffer Whether to automatically increase the image size to accomodate rotated corners. Default is false. Will create frames that are 150% larger on each axis than the original frame or graphic. + * @return This FlxEmitter instance (nice for chaining stuff together, if you're into that). + */ + public function makeParticles(Graphics:FlxGraphicAsset, Quantity:Int = 50, bakedRotationAngles:Int = 16, Multiple:Bool = false, Collide:Float = 0.8, AutoBuffer:Bool = false):FlxTypedEmitter + { + maxSize = Quantity; + var totalFrames:Int = 1; + + if (Multiple) + { + var sprite = new FlxSprite(); + sprite.loadGraphic(Graphics, true); + totalFrames = sprite.frames; + sprite.destroy(); + } + + var randomFrame:Int; + var particle:T; + var pClass:Class = particleClass; + var i:Int = 0; + + while (i < Quantity) + { + particle = Type.createInstance(pClass, []); + + if (Multiple) + { + randomFrame = FlxRandom.intRanged(0, totalFrames - 1); + + if (bakedRotationAngles > 0) + { + #if FLX_RENDER_BLIT + particle.loadRotatedGraphic(Graphics, bakedRotationAngles, randomFrame, false, AutoBuffer); + #else + particle.loadGraphic(Graphics, true); + #end + } + else + { + particle.loadGraphic(Graphics, true); + } + particle.animation.frameIndex = randomFrame; + } + else + { + if (bakedRotationAngles > 0) + { + #if FLX_RENDER_BLIT + particle.loadRotatedGraphic(Graphics, bakedRotationAngles, -1, false, AutoBuffer); + #else + particle.loadGraphic(Graphics); + #end + } + else + { + particle.loadGraphic(Graphics); + } + } + if (Collide > 0) + { + particle.width *= Collide; + particle.height *= Collide; + particle.centerOffsets(); + } + else + { + particle.allowCollisions = FlxObject.NONE; + } + + particle.exists = false; + add(particle); + i++; + } + return this; + } + + /** + * Called automatically by the game loop, decides when to launch particles and when to "die". + */ + override public function update():Void + { + if (emitting) + { + if (_explode) + { + emitting = false; + _waitForKill = true; + + var i:Int = 0; + var l:Int = _quantity; + + if ((l <= 0) || (l > length)) + { + l = length; + } + + while (i < l) + { + emitParticle(); + i++; + } + + _quantity = 0; + } + else + { + // Spawn a particle per frame + if (frequency <= 0) + { + emitParticle(); + + if ((_quantity > 0) && (++_counter >= _quantity)) + { + emitting = false; + _waitForKill = true; + _quantity = 0; + } + } + else + { + _timer += FlxG.elapsed; + + while (_timer > frequency) + { + _timer -= frequency; + emitParticle(); + + if ((_quantity > 0) && (++_counter >= _quantity)) + { + emitting = false; + _waitForKill = true; + _quantity = 0; + } + } + } + } + } + else if (_waitForKill) + { + _timer += FlxG.elapsed; + + if ((life.max > 0) && (_timer > life.max)) + { + kill(); + return; + } + } + + super.update(); + } + + /** + * Call this function to turn off all the particles and the emitter. + */ + override public function kill():Void + { + emitting = false; + _waitForKill = false; + + super.kill(); + } + + /** + * Call this function to start emitting particles. + * @param Explode Whether the particles should all burst out at once. + * @param Lifespan How long each particle lives once emitted. 0 = forever. + * @param Frequency Ignored if Explode is set to true. Frequency is how often to emit a particle. 0 = never emit, 0.1 = 1 particle every 0.1 seconds, 5 = 1 particle every 5 seconds. + * @param Quantity How many particles to launch. 0 = "all of the particles". + * @param LifespanRange Max amount to add to the particle's lifespan. Leave it to default (zero), if you want to make particle "live" forever (plus you should set Lifespan parameter to zero too). + */ + public function start(Explode:Bool = true, Lifespan:Float = 0, Frequency:Float = 0.1, Quantity:Int = 0, LifespanRange:Float = 0):Void + { + revive(); + visible = true; + emitting = true; + + _explode = Explode; + life.min = Lifespan; + life.max = Lifespan + Math.abs(LifespanRange); + frequency = Frequency; + _quantity += Quantity; + + _counter = 0; + _timer = 0; + + _waitForKill = false; + } + + /** + * This function can be used both internally and externally to emit the next particle. + */ + public function emitParticle():Void + { + var particle:T = cast recycle(cast particleClass); + particle.elasticity = bounce; + + particle.reset(x - (Std.int(particle.width) >> 1) + FlxRandom.float() * width, y - (Std.int(particle.height) >> 1) + FlxRandom.float() * height); + particle.visible = true; + + if (life.min != life.max) + { + particle.lifespan = particle.maxLifespan = life.min + FlxRandom.float() * (life.max - life.min); + } + else + { + particle.lifespan = particle.maxLifespan = life.min; + } + + if (startAlpha.min != startAlpha.max) + { + particle.startAlpha = startAlpha.min + FlxRandom.float() * (startAlpha.max - startAlpha.min); + } + else + { + particle.startAlpha = startAlpha.min; + } + particle.alpha = particle.startAlpha; + + var particleEndAlpha:Float = endAlpha.min; + if (endAlpha.min != endAlpha.max) + { + particleEndAlpha = endAlpha.min + FlxRandom.float() * (endAlpha.max - endAlpha.min); + } + + if (particleEndAlpha != particle.startAlpha) + { + particle.useFading = true; + particle.rangeAlpha = particleEndAlpha - particle.startAlpha; + } + else + { + particle.useFading = false; + particle.rangeAlpha = 0; + } + + // Particle color settings + var startRedComp:Float = particle.startRed = startRed.min; + var startGreenComp:Float = particle.startGreen = startGreen.min; + var startBlueComp:Float = particle.startBlue = startBlue.min; + + var endRedComp:Float = endRed.min; + var endGreenComp:Float = endGreen.min; + var endBlueComp:Float = endBlue.min; + + if (startRed.min != startRed.max) + { + particle.startRed = startRedComp = startRed.min + FlxRandom.float() * (startRed.max - startRed.min); + } + if (startGreen.min != startGreen.max) + { + particle.startGreen = startGreenComp = startGreen.min + FlxRandom.float() * (startGreen.max - startGreen.min); + } + if (startBlue.min != startBlue.max) + { + particle.startBlue = startBlueComp = startBlue.min + FlxRandom.float() * (startBlue.max - startBlue.min); + } + + if (endRed.min != endRed.max) + { + endRedComp = endRed.min + FlxRandom.float() * (endRed.max - endRed.min); + } + + if (endGreen.min != endGreen.max) + { + endGreenComp = endGreen.min + FlxRandom.float() * (endGreen.max - endGreen.min); + } + + if (endBlue.min != endBlue.max) + { + endBlueComp = endBlue.min + FlxRandom.float() * (endBlue.max - endBlue.min); + } + + particle.rangeRed = endRedComp - startRedComp; + particle.rangeGreen = endGreenComp - startGreenComp; + particle.rangeBlue = endBlueComp - startBlueComp; + + particle.useColoring = false; + + if (particle.rangeRed != 0 || particle.rangeGreen != 0 || particle.rangeBlue != 0) + { + particle.useColoring = true; + } + // End of particle color settings + if (startScale.min != startScale.max) + { + particle.startScale = startScale.min + FlxRandom.float() * (startScale.max - startScale.min); + } + else + { + particle.startScale = startScale.min; + } + particle.scale.x = particle.scale.y = particle.startScale; + + var particleEndScale:Float = endScale.min; + if (endScale.min != endScale.max) + { + particleEndScale = endScale.min + FlxRandom.intRanged(0, Std.int(endScale.max - endScale.min)); + } + + if (particleEndScale != particle.startScale) + { + particle.useScaling = true; + particle.rangeScale = particleEndScale - particle.startScale; + } + else + { + particle.useScaling = false; + particle.rangeScale = 0; + } + + particle.blend = blend; + + if (xVelocity.min != xVelocity.max) + { + particle.velocity.x = xVelocity.min + FlxRandom.float() * (xVelocity.max - xVelocity.min); + } + else + { + particle.velocity.x = xVelocity.min; + } + if (yVelocity.min != yVelocity.max) + { + particle.velocity.y = yVelocity.min + FlxRandom.float() * (yVelocity.max - yVelocity.min); + } + else + { + particle.velocity.y = yVelocity.min; + } + particle.acceleration.set(acceleration.x, acceleration.y); + + if (rotation.min != rotation.max) + { + particle.angularVelocity = rotation.min + FlxRandom.float() * (rotation.max - rotation.min); + } + else + { + particle.angularVelocity = rotation.min; + } + if (particle.angularVelocity != 0) + { + particle.angle = FlxRandom.float() * 360 - 180; + } + + particle.drag.set(particleDrag.x, particleDrag.y); + particle.onEmit(); + } + + /** + * A more compact way of setting the width and height of the emitter. + * + * @param Width The desired width of the emitter (particles are spawned randomly within these dimensions). + * @param Height The desired height of the emitter. + */ + public function setSize(Width:Int, Height:Int):Void + { + width = Width; + height = Height; + } + + /** + * A more compact way of setting the X velocity range of the emitter. + * + * @param Min The minimum value for this range. + * @param Max The maximum value for this range. + */ + public function setXSpeed(Min:Float = 0, Max:Float = 0):Void + { + if (Max < Min) + { + Max = Min; + } + + xVelocity.min = Min; + xVelocity.max = Max; + } + + /** + * A more compact way of setting the Y velocity range of the emitter. + * + * @param Min The minimum value for this range. + * @param Max The maximum value for this range. + */ + public function setYSpeed(Min:Float = 0, Max:Float = 0):Void + { + if (Max < Min) + { + Max = Min; + } + + yVelocity.min = Min; + yVelocity.max = Max; + } + + /** + * A more compact way of setting the angular velocity constraints of the emitter. + * + * @param Min The minimum value for this range. + * @param Max The maximum value for this range. + */ + public function setRotation(Min:Float = 0, Max:Float = 0):Void + { + if (Max < Min) + { + Max = Min; + } + + rotation.min = Min; + rotation.max = Max; + } + + /** + * A more compact way of setting the scale constraints of the emitter. + * + * @param StartMin The minimum value for particle scale at the start (emission). + * @param StartMax The maximum value for particle scale at the start (emission). + * @param EndMin The minimum value for particle scale at the end (death). + * @param EndMax The maximum value for particle scale at the end (death). + */ + public function setScale(StartMin:Float = 1, StartMax:Float = 1, EndMin:Float = 1, EndMax:Float = 1):Void + { + if (StartMax < StartMin) + { + StartMax = StartMin; + } + + if (EndMax < EndMin) + { + EndMax = EndMin; + } + + startScale.min = StartMin; + startScale.max = StartMax; + endScale.min = EndMin; + endScale.max = EndMax; + } + + /** + * A more compact way of setting the alpha constraints of the emitter. + * + * @param StartMin The minimum value for particle alpha at the start (emission). + * @param StartMax The maximum value for particle alpha at the start (emission). + * @param EndMin The minimum value for particle alpha at the end (death). + * @param EndMax The maximum value for particle alpha at the end (death). + */ + public function setAlpha(StartMin:Float = 1, StartMax:Float = 1, EndMin:Float = 1, EndMax:Float = 1):Void + { + if (StartMin < 0) + { + StartMin = 0; + } + + if (StartMax < StartMin) + { + StartMax = StartMin; + } + + if (EndMin < 0) + { + EndMin = 0; + } + + if (EndMax < EndMin) + { + EndMax = EndMin; + } + + startAlpha.min = StartMin; + startAlpha.max = StartMax; + endAlpha.min = EndMin; + endAlpha.max = EndMax; + } + + /** + * A more compact way of setting the color constraints of the emitter. + * But it's not so flexible as setting values of each color bounds objects. + * + * @param Start The start particles color at the start (emission). + * @param EndMin The end particles color at the end (death). + */ + public function setColor(Start:Int = 0xffffff, End:Int = 0xffffff):Void + { + Start &= 0xffffff; + End &= 0xffffff; + + var startRedComp:Float = (Start >> 16 & 0xFF) / 255; + var startGreenComp:Float = (Start >> 8 & 0xFF) / 255; + var startBlueComp:Float = (Start & 0xFF) / 255; + + var endRedComp:Float = (End >> 16 & 0xFF) / 255; + var endGreenComp:Float = (End >> 8 & 0xFF) / 255; + var endBlueComp:Float = (End & 0xFF) / 255; + + startRed.min = startRed.max = startRedComp; + startGreen.min = startGreen.max = startGreenComp; + startBlue.min = startBlue.max = startBlueComp; + + endRed.min = endRed.max = endRedComp; + endGreen.min = endGreen.max = endGreenComp; + endBlue.min = endBlue.max = endBlueComp; + } + + /** + * Change the emitter's midpoint to match the midpoint of a FlxObject. + * + * @param Object The FlxObject that you want to sync up with. + */ + public function focusOn(Object:FlxObject):Void + { + Object.getMidpoint(_point); + + x = _point.x - (Std.int(width) >> 1); + y = _point.y - (Std.int(height) >> 1); + } + + /** + * Helper function to set the coordinates of this object. + * Handy since it only requires one line of code. + * + * @param X The new x position + * @param Y The new y position + */ + public inline function setPosition(X:Float = 0, Y:Float = 0):Void + { + x = X; + y = Y; + } + + private inline function get_width():Float + { + return xPosition.max; + } + + private inline function set_width(Value:Float):Float + { + return xPosition.max = Value; + } + + private inline function get_height():Float + { + return yPosition.max; + } + + private inline function set_height(Value:Float):Float + { + return yPosition.max = Value; + } + + private inline function get_x():Float + { + return xPosition.min; + } + + private inline function set_x(Value:Float):Float + { + return xPosition.min = Value; + } + + private inline function get_y():Float + { + return yPosition.min; + } + + private inline function set_y(Value:Float):Float + { + return yPosition.min = Value; + } + + private inline function get_gravity():Float + { + return acceleration.y; + } + + private inline function set_gravity(Value:Float):Float + { + return acceleration.y = Value; + } + + private inline function get_minRotation():Float + { + return rotation.min; + } + + private inline function set_minRotation(Value:Float):Float + { + return rotation.min = Value; + } + + private inline function get_maxRotation():Float + { + return rotation.max; + } + + private inline function set_maxRotation(Value:Float):Float + { + return rotation.max = Value; + } + + private inline function get_lifespan():Float + { + return life.min; + } + + private function set_lifespan(Value:Float):Float + { + var dl:Float = life.max - life.min; + life.min = Value; + life.max = Value + dl; + + return Value; + } +} + +/** + * Helper object for holding bounds of different variables + */ +class Bounds +{ + public var min:T; + public var max:T; + + public function new(min:T, ?max:Null) + { + set(min, max); + } + + public function set(min:T, ?max:Null):Bounds + { + this.min = min; + this.max = max == null ? min : max; + return this; + } + + public function toString():String + { + return FlxStringUtil.getDebugString([ + LabelValuePair.weak("min", min), + LabelValuePair.weak("max", max)]); + } +} \ No newline at end of file diff --git a/flixel/effects/particles/FlxEmitterExt.hx b/flixel/effects/particles/FlxEmitterExt.hx index 84a4b2054f..87c23f4a0f 100644 --- a/flixel/effects/particles/FlxEmitterExt.hx +++ b/flixel/effects/particles/FlxEmitterExt.hx @@ -1,3 +1,276 @@ package flixel.effects.particles; -typedef FlxEmitterExt = FlxTypedEmitterExt; \ No newline at end of file +import flixel.effects.particles.FlxEmitterExt.FlxTypedEmitterExt; +import flixel.FlxSprite; +import flixel.math.FlxRandom; +import flixel.effects.particles.FlxParticle; +import flixel.effects.particles.FlxEmitter; + +typedef FlxEmitterExt = FlxTypedEmitterExt; + +/** + * Extended FlxEmitter that emits particles in a circle (instead of a square). + * It also provides a new function setMotion to control particle behavior even more. + * This was inspired by the way Chevy Ray Johnston implemented his particle emitter in Flashpunk. + * @author Dirk Bunk + */ +class FlxTypedEmitterExt extends FlxTypedEmitter +{ + /** + * Launch Direction. + */ + public var angle:Float; + /** + * Distance to travel. + */ + public var distance:Float; + /** + * Random amount to add to the particle's direction. + */ + public var angleRange:Float; + /** + * Random amount to add to the particle's distance. + */ + public var distanceRange:Float; + + /** + * Creates a new FlxTypedEmitterExt object at a specific position. + * Does NOT automatically generate or attach particles! + * + * @param X The X position of the emitter. + * @param Y The Y position of the emitter. + * @param Size Optional, specifies a maximum capacity for this emitter. + */ + public function new(X:Float = 0, Y:Float = 0, Size:Int = 0) + { + super(X, Y, Size); + + // Set defaults + setMotion(0, 0, 0.5, 360, 100, 1.5); + } + + /** + * Defines the motion range for this emitter. + * + * @param Angle Launch Direction. + * @param Distance Distance to travel. + * @param Lifespan 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 LifespanRange Random amount to add to the particle's duration. + */ + public function setMotion(Angle:Float, Distance:Float, Lifespan:Float, AngleRange:Float = 0, DistanceRange:Float = 0, LifespanRange:Float = 0):Void + { + angle = Angle * 0.017453293; + distance = Distance; + life.min = Lifespan; + life.max = Lifespan + LifespanRange; + angleRange = AngleRange * 0.017453293; + distanceRange = DistanceRange; + } + + /** + * Defines the motion range for a specific particle. + * + * @param Particle The Particle to set the motion for + * @param Angle Launch Direction. + * @param Distance Distance to travel. + * @param Lifespan 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 LifespanRange Random amount to add to the particle's duration. + */ + private function setParticleMotion(Particle:T, Angle:Float, Distance:Float, AngleRange:Float = 0, DistanceRange:Float = 0):Void + { + //set particle direction and speed + var a:Float = FlxRandom.floatRanged(Angle, Angle + AngleRange); + var d:Float = FlxRandom.floatRanged(Distance, Distance + DistanceRange); + + Particle.velocity.x = Math.cos(a) * d; + Particle.velocity.y = Math.sin(a) * d; + } + + /** + * Call this function to start emitting particles. + * + * @param Explode Whether the particles should all burst out at once. + * @param Lifespan Unused parameter due to class override. Use setMotion to set things like a particle's lifespan. + * @param Frequency Ignored if Explode is set to true. Frequency is how often to emit a particle. 0 = never emit, 0.1 = 1 particle every 0.1 seconds, 5 = 1 particle every 5 seconds. + * @param Quantity How many particles to launch. 0 = "all of the particles". + * @param LifespanRange Max amount to add to the particle's lifespan. Leave it to default (zero), if you want to make particle "live" forever (plus you should set Lifespan parameter to zero too). + */ + override public function start(Explode:Bool = true, Lifespan:Float = 0, Frequency:Float = 0.1, Quantity:Int = 0, LifespanRange:Float = 0):Void + { + super.start(Explode, Lifespan, Frequency, Quantity, LifespanRange); + + // Immediately execute the explosion code part from the update function, to prevent other explosions to override this one. + // This fixes the problem that you can not add two particle explosions in the same frame. + if (Explode) + { + emitting = false; + + var i:Int = 0; + var l:Int = _quantity; + + if ((l <= 0) || (l > members.length)) + { + l = members.length; + } + + while (i < l) + { + emitParticle(); + i++; + } + + _quantity = 0; + } + } + + /** + * This function can be used both internally and externally to emit the next particle. + */ + override public function emitParticle():Void + { + var particle:T = cast recycle(cast particleClass); + particle.elasticity = bounce; + + particle.reset(x - (Std.int(particle.width) >> 1) + FlxRandom.float() * width, y - (Std.int(particle.height) >> 1) + FlxRandom.float() * height); + particle.visible = true; + + if (life.min != life.max) + { + particle.lifespan = particle.maxLifespan = life.min + FlxRandom.float() * (life.max - life.min); + } + else + { + particle.lifespan = particle.maxLifespan = life.min; + } + + if (startAlpha.min != startAlpha.max) + { + particle.startAlpha = startAlpha.min + FlxRandom.float() * (startAlpha.max - startAlpha.min); + } + else + { + particle.startAlpha = startAlpha.min; + } + particle.alpha = particle.startAlpha; + + var particleEndAlpha:Float = endAlpha.min; + if (endAlpha.min != endAlpha.max) + { + particleEndAlpha = endAlpha.min + FlxRandom.float() * (endAlpha.max - endAlpha.min); + } + + if (particleEndAlpha != particle.startAlpha) + { + particle.useFading = true; + particle.rangeAlpha = particleEndAlpha - particle.startAlpha; + } + else + { + particle.useFading = false; + particle.rangeAlpha = 0; + } + + // particle color settings + var startRedComp:Float = particle.startRed = startRed.min; + var startGreenComp:Float = particle.startGreen = startGreen.min; + var startBlueComp:Float = particle.startBlue = startBlue.min; + + var endRedComp:Float = endRed.min; + var endGreenComp:Float = endGreen.min; + var endBlueComp:Float = endBlue.min; + + if (startRed.min != startRed.max) + { + particle.startRed = startRedComp = startRed.min + FlxRandom.float() * (startRed.max - startRed.min); + } + if (startGreen.min != startGreen.max) + { + particle.startGreen = startGreenComp = startGreen.min + FlxRandom.float() * (startGreen.max - startGreen.min); + } + if (startBlue.min != startBlue.max) + { + particle.startBlue = startBlueComp = startBlue.min + FlxRandom.float() * (startBlue.max - startBlue.min); + } + + if (endRed.min != endRed.max) + { + endRedComp = endRed.min + FlxRandom.float() * (endRed.max - endRed.min); + } + + if (endGreen.min != endGreen.max) + { + endGreenComp = endGreen.min + FlxRandom.float() * (endGreen.max - endGreen.min); + } + + if (endBlue.min != endBlue.max) + { + endBlueComp = endBlue.min + FlxRandom.float() * (endBlue.max - endBlue.min); + } + + particle.rangeRed = endRedComp - startRedComp; + particle.rangeGreen = endGreenComp - startGreenComp; + particle.rangeBlue = endBlueComp - startBlueComp; + + particle.useColoring = false; + + if (particle.rangeRed != 0 || particle.rangeGreen != 0 || particle.rangeBlue != 0) + { + particle.useColoring = true; + } + + // End of particle color settings + if (startScale.min != startScale.max) + { + particle.startScale = startScale.min + FlxRandom.float() * (startScale.max - startScale.min); + } + else + { + particle.startScale = startScale.min; + } + particle.scale.x = particle.scale.y = particle.startScale; + + var particleEndScale:Float = endScale.min; + + if (endScale.min != endScale.max) + { + particleEndScale = endScale.min + Std.int(FlxRandom.float() * (endScale.max - endScale.min)); + } + + if (particleEndScale != particle.startScale) + { + particle.useScaling = true; + particle.rangeScale = particleEndScale - particle.startScale; + } + else + { + particle.useScaling = false; + particle.rangeScale = 0; + } + + particle.blend = blend; + + // Set particle motion + setParticleMotion(particle, angle, distance, angleRange, distanceRange); + particle.acceleration.set(acceleration.x, acceleration.y); + + if (rotation.min != rotation.max) + { + particle.angularVelocity = rotation.min + FlxRandom.float() * (rotation.max - rotation.min); + } + else + { + particle.angularVelocity = rotation.min; + } + if (particle.angularVelocity != 0) + { + particle.angle = FlxRandom.float() * 360 - 180; + } + + particle.drag.set(particleDrag.x, particleDrag.y); + particle.onEmit(); + } +} \ No newline at end of file diff --git a/flixel/effects/particles/FlxTypedEmitter.hx b/flixel/effects/particles/FlxTypedEmitter.hx deleted file mode 100644 index ac6cbbfab4..0000000000 --- a/flixel/effects/particles/FlxTypedEmitter.hx +++ /dev/null @@ -1,897 +0,0 @@ -package flixel.effects.particles; - -import flash.display.BlendMode; -import flixel.effects.particles.FlxParticle; -import flixel.FlxG; -import flixel.FlxObject; -import flixel.FlxSprite; -import flixel.group.FlxTypedGroup; -import flixel.system.FlxAssets.FlxGraphicAsset; -import flixel.util.FlxDestroyUtil; -import flixel.math.FlxPoint; -import flixel.math.FlxRandom; -import flixel.util.FlxStringUtil; - -/** - * FlxTypedEmitter is a lightweight particle emitter. - * It can be used for one-time explosions or for - * continuous fx like rain and fire. FlxEmitter - * is not optimized or anything; all it does is launch - * FlxParticle objects out at set intervals - * by setting their positions and velocities accordingly. - * It is easy to use and relatively efficient, - * relying on FlxGroup's RECYCLE POWERS. - */ -class FlxTypedEmitter extends FlxTypedGroup -{ - /** - * Set your own particle class type here. The custom class must extend FlxParticle. - * Default is FlxParticle. - */ - public var particleClass:Class; - /** - * Determines whether the emitter is currently emitting particles. - * It is totally safe to directly toggle this. - */ - public var emitting:Bool = false; - /** - * How often a particle is emitted (if emitter is started with Explode == false). - */ - public var frequency:Float = 0.1; - /** - * Sets particle's blend mode. null by default. - * Warning: expensive on flash target - */ - public var blend:BlendMode; - /** - * How much each particle should bounce. 1 = full bounce, 0 = no bounce. - */ - public var bounce:Float = 0; - - /** - * The width of the emitter. Particles can be randomly generated from anywhere within this box. - */ - public var width(get, set):Float; - /** - * The height of the emitter. Particles can be randomly generated from anywhere within this box. - */ - public var height(get, set):Float; - /** - * The x position of this emitter. - */ - public var x(get, set):Float; - /** - * The y position of this emitter. - */ - public var y(get, set):Float; - /** - * Sets the acceleration.y member of each particle to this value on launch. - */ - public var gravity(get, set):Float; - /** - * The minimum possible angular velocity of a particle. The default value is -360. - * NOTE: rotating particles are more expensive to draw than non-rotating ones! - */ - public var minRotation(get, set):Float; - /** - * The maximum possible angular velocity of a particle. The default value is 360. - * NOTE: rotating particles are more expensive to draw than non-rotating ones! - */ - public var maxRotation(get, set):Float; - /** - * How long each particle lives once it is emitted. - * Set lifespan to 'zero' for particles to live forever. - */ - public var lifespan(get, set):Float; - /** - * The x position range of the emitter in world space. - */ - public var xPosition(default, null):Bounds; - /** - * The y position range of emitter in world space. - */ - public var yPosition(default, null):Bounds; - /** - * The x velocity range of a particle. - * The default value is (-100,-100). - */ - public var xVelocity(default, null):Bounds; - /** - * The y velocity range of a particle. - * The default value is (100,100). - */ - public var yVelocity(default, null):Bounds; - /** - * The minimum and maximum possible angular velocity of a particle. The default value is (-360, 360). - * NOTE: rotating particles are more expensive to draw than non-rotating ones! - */ - public var rotation(default, null):Bounds; - public var life(default, null):Bounds; - /** - * Sets start scale range (when particle emits) - */ - public var startScale(default, null):Bounds; - /** - * Sets end scale range (when particle dies) - */ - public var endScale(default, null):Bounds; - /** - * Sets start alpha range (when particle emits) - */ - public var startAlpha(default, null):Bounds; - /** - * Sets end alpha range (when particle emits) - */ - public var endAlpha(default, null):Bounds; - /** - * Sets start red color component range (when particle emits) - */ - public var startRed(default, null):Bounds; - /** - * Sets start green color component range (when particle emits) - */ - public var startGreen(default, null):Bounds; - /** - * Sets start blue color component range (when particle emits) - */ - public var startBlue(default, null):Bounds; - /** - * Sets end red color component range (when particle emits) - */ - public var endRed(default, null):Bounds; - /** - * Sets end green color component range (when particle emits) - */ - public var endGreen(default, null):Bounds; - /** - * Sets end blue color component range (when particle emits) - */ - public var endBlue(default, null):Bounds; - /** - * The X and Y drag component of particles launched from the emitter. - */ - public var particleDrag(default, null):FlxPoint; - /** - * Sets the acceleration member of each particle to this value on launch. - */ - public var acceleration(default, null):FlxPoint; - - /** - * Internal helper for deciding how many particles to launch. - */ - private var _quantity:Int = 0; - /** - * Internal helper for the style of particle emission (all at once, or one at a time). - */ - private var _explode:Bool = true; - /** - * Internal helper for deciding when to launch particles or kill them. - */ - private var _timer:Float = 0; - /** - * Internal counter for figuring out how many particles to launch. - */ - private var _counter:Int = 0; - /** - * Internal point object, handy for reusing for memory mgmt purposes. - */ - private var _point:FlxPoint; - /** - * Internal helper for automatic call the kill() method - */ - private var _waitForKill:Bool = false; - - /** - * Creates a new FlxTypedEmitter object at a specific position. - * Does NOT automatically generate or attach particles! - * - * @param X The X position of the emitter. - * @param Y The Y position of the emitter. - * @param Size Optional, specifies a maximum capacity for this emitter. - */ - public function new(X:Float = 0, Y:Float = 0, Size:Int = 0) - { - super(Size); - - xPosition = new Bounds(X, 0); - yPosition = new Bounds(Y, 0); - xVelocity = new Bounds( -100, 100); - yVelocity = new Bounds( -100, 100); - rotation = new Bounds( -360, 360); - startScale = new Bounds(1, 1); - endScale = new Bounds(1, 1); - startAlpha = new Bounds(1.0, 1.0); - endAlpha = new Bounds(1.0, 1.0); - startRed = new Bounds(1.0, 1.0); - startGreen = new Bounds(1.0, 1.0); - startBlue = new Bounds(1.0, 1.0); - endRed = new Bounds(1.0, 1.0); - endGreen = new Bounds(1.0, 1.0); - endBlue = new Bounds(1.0, 1.0); - - acceleration = FlxPoint.get(0, 0); - particleClass = cast FlxParticle; - particleDrag = FlxPoint.get(); - - life = new Bounds(3, 3); - exists = false; - _point = FlxPoint.get(); - } - - /** - * Clean up memory. - */ - override public function destroy():Void - { - _point = FlxDestroyUtil.put(_point); - acceleration = FlxDestroyUtil.put(acceleration); - particleDrag = FlxDestroyUtil.put(particleDrag); - - xPosition = null; - yPosition = null; - xVelocity = null; - yVelocity = null; - rotation = null; - startScale = null; - endScale = null; - startAlpha = null; - endAlpha = null; - startRed = null; - startGreen = null; - startBlue = null; - endRed = null; - endGreen = null; - endBlue = null; - blend = null; - _point = null; - - super.destroy(); - } - - /** - * This function generates a new array of particle sprites to attach to the emitter. - * - * @param Graphics If you opted to not pre-configure an array of FlxParticle objects, you can simply pass in a particle image or sprite sheet. - * @param Quantity The number of particles to generate when using the "create from image" option. - * @param BakedRotations How many frames of baked rotation to use (boosts performance). Set to zero to not use baked rotations. - * @param Multiple Whether the image in the Graphics param is a single particle or a bunch of particles (if it's a bunch, they need to be square!). - * @param Collide Whether the particles should be flagged as not 'dead' (non-colliding particles are higher performance). 0 means no collisions, 0-1 controls scale of particle's bounding box. - * @param AutoBuffer Whether to automatically increase the image size to accomodate rotated corners. Default is false. Will create frames that are 150% larger on each axis than the original frame or graphic. - * @return This FlxEmitter instance (nice for chaining stuff together, if you're into that). - */ - public function makeParticles(Graphics:FlxGraphicAsset, Quantity:Int = 50, bakedRotationAngles:Int = 16, Multiple:Bool = false, Collide:Float = 0.8, AutoBuffer:Bool = false):FlxTypedEmitter - { - maxSize = Quantity; - var totalFrames:Int = 1; - - if (Multiple) - { - var sprite = new FlxSprite(); - sprite.loadGraphic(Graphics, true); - totalFrames = sprite.frames; - sprite.destroy(); - } - - var randomFrame:Int; - var particle:T; - var pClass:Class = particleClass; - var i:Int = 0; - - while (i < Quantity) - { - particle = Type.createInstance(pClass, []); - - if (Multiple) - { - randomFrame = FlxRandom.intRanged(0, totalFrames - 1); - - if (bakedRotationAngles > 0) - { - #if FLX_RENDER_BLIT - particle.loadRotatedGraphic(Graphics, bakedRotationAngles, randomFrame, false, AutoBuffer); - #else - particle.loadGraphic(Graphics, true); - #end - } - else - { - particle.loadGraphic(Graphics, true); - } - particle.animation.frameIndex = randomFrame; - } - else - { - if (bakedRotationAngles > 0) - { - #if FLX_RENDER_BLIT - particle.loadRotatedGraphic(Graphics, bakedRotationAngles, -1, false, AutoBuffer); - #else - particle.loadGraphic(Graphics); - #end - } - else - { - particle.loadGraphic(Graphics); - } - } - if (Collide > 0) - { - particle.width *= Collide; - particle.height *= Collide; - particle.centerOffsets(); - } - else - { - particle.allowCollisions = FlxObject.NONE; - } - - particle.exists = false; - add(particle); - i++; - } - return this; - } - - /** - * Called automatically by the game loop, decides when to launch particles and when to "die". - */ - override public function update():Void - { - if (emitting) - { - if (_explode) - { - emitting = false; - _waitForKill = true; - - var i:Int = 0; - var l:Int = _quantity; - - if ((l <= 0) || (l > length)) - { - l = length; - } - - while (i < l) - { - emitParticle(); - i++; - } - - _quantity = 0; - } - else - { - // Spawn a particle per frame - if (frequency <= 0) - { - emitParticle(); - - if ((_quantity > 0) && (++_counter >= _quantity)) - { - emitting = false; - _waitForKill = true; - _quantity = 0; - } - } - else - { - _timer += FlxG.elapsed; - - while (_timer > frequency) - { - _timer -= frequency; - emitParticle(); - - if ((_quantity > 0) && (++_counter >= _quantity)) - { - emitting = false; - _waitForKill = true; - _quantity = 0; - } - } - } - } - } - else if (_waitForKill) - { - _timer += FlxG.elapsed; - - if ((life.max > 0) && (_timer > life.max)) - { - kill(); - return; - } - } - - super.update(); - } - - /** - * Call this function to turn off all the particles and the emitter. - */ - override public function kill():Void - { - emitting = false; - _waitForKill = false; - - super.kill(); - } - - /** - * Call this function to start emitting particles. - * @param Explode Whether the particles should all burst out at once. - * @param Lifespan How long each particle lives once emitted. 0 = forever. - * @param Frequency Ignored if Explode is set to true. Frequency is how often to emit a particle. 0 = never emit, 0.1 = 1 particle every 0.1 seconds, 5 = 1 particle every 5 seconds. - * @param Quantity How many particles to launch. 0 = "all of the particles". - * @param LifespanRange Max amount to add to the particle's lifespan. Leave it to default (zero), if you want to make particle "live" forever (plus you should set Lifespan parameter to zero too). - */ - public function start(Explode:Bool = true, Lifespan:Float = 0, Frequency:Float = 0.1, Quantity:Int = 0, LifespanRange:Float = 0):Void - { - revive(); - visible = true; - emitting = true; - - _explode = Explode; - life.min = Lifespan; - life.max = Lifespan + Math.abs(LifespanRange); - frequency = Frequency; - _quantity += Quantity; - - _counter = 0; - _timer = 0; - - _waitForKill = false; - } - - /** - * This function can be used both internally and externally to emit the next particle. - */ - public function emitParticle():Void - { - var particle:T = cast recycle(cast particleClass); - particle.elasticity = bounce; - - particle.reset(x - (Std.int(particle.width) >> 1) + FlxRandom.float() * width, y - (Std.int(particle.height) >> 1) + FlxRandom.float() * height); - particle.visible = true; - - if (life.min != life.max) - { - particle.lifespan = particle.maxLifespan = life.min + FlxRandom.float() * (life.max - life.min); - } - else - { - particle.lifespan = particle.maxLifespan = life.min; - } - - if (startAlpha.min != startAlpha.max) - { - particle.startAlpha = startAlpha.min + FlxRandom.float() * (startAlpha.max - startAlpha.min); - } - else - { - particle.startAlpha = startAlpha.min; - } - particle.alpha = particle.startAlpha; - - var particleEndAlpha:Float = endAlpha.min; - if (endAlpha.min != endAlpha.max) - { - particleEndAlpha = endAlpha.min + FlxRandom.float() * (endAlpha.max - endAlpha.min); - } - - if (particleEndAlpha != particle.startAlpha) - { - particle.useFading = true; - particle.rangeAlpha = particleEndAlpha - particle.startAlpha; - } - else - { - particle.useFading = false; - particle.rangeAlpha = 0; - } - - // Particle color settings - var startRedComp:Float = particle.startRed = startRed.min; - var startGreenComp:Float = particle.startGreen = startGreen.min; - var startBlueComp:Float = particle.startBlue = startBlue.min; - - var endRedComp:Float = endRed.min; - var endGreenComp:Float = endGreen.min; - var endBlueComp:Float = endBlue.min; - - if (startRed.min != startRed.max) - { - particle.startRed = startRedComp = startRed.min + FlxRandom.float() * (startRed.max - startRed.min); - } - if (startGreen.min != startGreen.max) - { - particle.startGreen = startGreenComp = startGreen.min + FlxRandom.float() * (startGreen.max - startGreen.min); - } - if (startBlue.min != startBlue.max) - { - particle.startBlue = startBlueComp = startBlue.min + FlxRandom.float() * (startBlue.max - startBlue.min); - } - - if (endRed.min != endRed.max) - { - endRedComp = endRed.min + FlxRandom.float() * (endRed.max - endRed.min); - } - - if (endGreen.min != endGreen.max) - { - endGreenComp = endGreen.min + FlxRandom.float() * (endGreen.max - endGreen.min); - } - - if (endBlue.min != endBlue.max) - { - endBlueComp = endBlue.min + FlxRandom.float() * (endBlue.max - endBlue.min); - } - - particle.rangeRed = endRedComp - startRedComp; - particle.rangeGreen = endGreenComp - startGreenComp; - particle.rangeBlue = endBlueComp - startBlueComp; - - particle.useColoring = false; - - if (particle.rangeRed != 0 || particle.rangeGreen != 0 || particle.rangeBlue != 0) - { - particle.useColoring = true; - } - // End of particle color settings - if (startScale.min != startScale.max) - { - particle.startScale = startScale.min + FlxRandom.float() * (startScale.max - startScale.min); - } - else - { - particle.startScale = startScale.min; - } - particle.scale.x = particle.scale.y = particle.startScale; - - var particleEndScale:Float = endScale.min; - if (endScale.min != endScale.max) - { - particleEndScale = endScale.min + FlxRandom.intRanged(0, Std.int(endScale.max - endScale.min)); - } - - if (particleEndScale != particle.startScale) - { - particle.useScaling = true; - particle.rangeScale = particleEndScale - particle.startScale; - } - else - { - particle.useScaling = false; - particle.rangeScale = 0; - } - - particle.blend = blend; - - if (xVelocity.min != xVelocity.max) - { - particle.velocity.x = xVelocity.min + FlxRandom.float() * (xVelocity.max - xVelocity.min); - } - else - { - particle.velocity.x = xVelocity.min; - } - if (yVelocity.min != yVelocity.max) - { - particle.velocity.y = yVelocity.min + FlxRandom.float() * (yVelocity.max - yVelocity.min); - } - else - { - particle.velocity.y = yVelocity.min; - } - particle.acceleration.set(acceleration.x, acceleration.y); - - if (rotation.min != rotation.max) - { - particle.angularVelocity = rotation.min + FlxRandom.float() * (rotation.max - rotation.min); - } - else - { - particle.angularVelocity = rotation.min; - } - if (particle.angularVelocity != 0) - { - particle.angle = FlxRandom.float() * 360 - 180; - } - - particle.drag.set(particleDrag.x, particleDrag.y); - particle.onEmit(); - } - - /** - * A more compact way of setting the width and height of the emitter. - * - * @param Width The desired width of the emitter (particles are spawned randomly within these dimensions). - * @param Height The desired height of the emitter. - */ - public function setSize(Width:Int, Height:Int):Void - { - width = Width; - height = Height; - } - - /** - * A more compact way of setting the X velocity range of the emitter. - * - * @param Min The minimum value for this range. - * @param Max The maximum value for this range. - */ - public function setXSpeed(Min:Float = 0, Max:Float = 0):Void - { - if (Max < Min) - { - Max = Min; - } - - xVelocity.min = Min; - xVelocity.max = Max; - } - - /** - * A more compact way of setting the Y velocity range of the emitter. - * - * @param Min The minimum value for this range. - * @param Max The maximum value for this range. - */ - public function setYSpeed(Min:Float = 0, Max:Float = 0):Void - { - if (Max < Min) - { - Max = Min; - } - - yVelocity.min = Min; - yVelocity.max = Max; - } - - /** - * A more compact way of setting the angular velocity constraints of the emitter. - * - * @param Min The minimum value for this range. - * @param Max The maximum value for this range. - */ - public function setRotation(Min:Float = 0, Max:Float = 0):Void - { - if (Max < Min) - { - Max = Min; - } - - rotation.min = Min; - rotation.max = Max; - } - - /** - * A more compact way of setting the scale constraints of the emitter. - * - * @param StartMin The minimum value for particle scale at the start (emission). - * @param StartMax The maximum value for particle scale at the start (emission). - * @param EndMin The minimum value for particle scale at the end (death). - * @param EndMax The maximum value for particle scale at the end (death). - */ - public function setScale(StartMin:Float = 1, StartMax:Float = 1, EndMin:Float = 1, EndMax:Float = 1):Void - { - if (StartMax < StartMin) - { - StartMax = StartMin; - } - - if (EndMax < EndMin) - { - EndMax = EndMin; - } - - startScale.min = StartMin; - startScale.max = StartMax; - endScale.min = EndMin; - endScale.max = EndMax; - } - - /** - * A more compact way of setting the alpha constraints of the emitter. - * - * @param StartMin The minimum value for particle alpha at the start (emission). - * @param StartMax The maximum value for particle alpha at the start (emission). - * @param EndMin The minimum value for particle alpha at the end (death). - * @param EndMax The maximum value for particle alpha at the end (death). - */ - public function setAlpha(StartMin:Float = 1, StartMax:Float = 1, EndMin:Float = 1, EndMax:Float = 1):Void - { - if (StartMin < 0) - { - StartMin = 0; - } - - if (StartMax < StartMin) - { - StartMax = StartMin; - } - - if (EndMin < 0) - { - EndMin = 0; - } - - if (EndMax < EndMin) - { - EndMax = EndMin; - } - - startAlpha.min = StartMin; - startAlpha.max = StartMax; - endAlpha.min = EndMin; - endAlpha.max = EndMax; - } - - /** - * A more compact way of setting the color constraints of the emitter. - * But it's not so flexible as setting values of each color bounds objects. - * - * @param Start The start particles color at the start (emission). - * @param EndMin The end particles color at the end (death). - */ - public function setColor(Start:Int = 0xffffff, End:Int = 0xffffff):Void - { - Start &= 0xffffff; - End &= 0xffffff; - - var startRedComp:Float = (Start >> 16 & 0xFF) / 255; - var startGreenComp:Float = (Start >> 8 & 0xFF) / 255; - var startBlueComp:Float = (Start & 0xFF) / 255; - - var endRedComp:Float = (End >> 16 & 0xFF) / 255; - var endGreenComp:Float = (End >> 8 & 0xFF) / 255; - var endBlueComp:Float = (End & 0xFF) / 255; - - startRed.min = startRed.max = startRedComp; - startGreen.min = startGreen.max = startGreenComp; - startBlue.min = startBlue.max = startBlueComp; - - endRed.min = endRed.max = endRedComp; - endGreen.min = endGreen.max = endGreenComp; - endBlue.min = endBlue.max = endBlueComp; - } - - /** - * Change the emitter's midpoint to match the midpoint of a FlxObject. - * - * @param Object The FlxObject that you want to sync up with. - */ - public function focusOn(Object:FlxObject):Void - { - Object.getMidpoint(_point); - - x = _point.x - (Std.int(width) >> 1); - y = _point.y - (Std.int(height) >> 1); - } - - /** - * Helper function to set the coordinates of this object. - * Handy since it only requires one line of code. - * - * @param X The new x position - * @param Y The new y position - */ - public inline function setPosition(X:Float = 0, Y:Float = 0):Void - { - x = X; - y = Y; - } - - private inline function get_width():Float - { - return xPosition.max; - } - - private inline function set_width(Value:Float):Float - { - return xPosition.max = Value; - } - - private inline function get_height():Float - { - return yPosition.max; - } - - private inline function set_height(Value:Float):Float - { - return yPosition.max = Value; - } - - private inline function get_x():Float - { - return xPosition.min; - } - - private inline function set_x(Value:Float):Float - { - return xPosition.min = Value; - } - - private inline function get_y():Float - { - return yPosition.min; - } - - private inline function set_y(Value:Float):Float - { - return yPosition.min = Value; - } - - private inline function get_gravity():Float - { - return acceleration.y; - } - - private inline function set_gravity(Value:Float):Float - { - return acceleration.y = Value; - } - - private inline function get_minRotation():Float - { - return rotation.min; - } - - private inline function set_minRotation(Value:Float):Float - { - return rotation.min = Value; - } - - private inline function get_maxRotation():Float - { - return rotation.max; - } - - private inline function set_maxRotation(Value:Float):Float - { - return rotation.max = Value; - } - - private inline function get_lifespan():Float - { - return life.min; - } - - private function set_lifespan(Value:Float):Float - { - var dl:Float = life.max - life.min; - life.min = Value; - life.max = Value + dl; - - return Value; - } -} - -/** - * Helper object for holding bounds of different variables - */ -class Bounds -{ - public var min:T; - public var max:T; - - public function new(min:T, ?max:Null) - { - set(min, max); - } - - public function set(min:T, ?max:Null):Bounds - { - this.min = min; - this.max = max == null ? min : max; - return this; - } - - public function toString():String - { - return FlxStringUtil.getDebugString([ - LabelValuePair.weak("min", min), - LabelValuePair.weak("max", max)]); - } -} \ No newline at end of file diff --git a/flixel/effects/particles/FlxTypedEmitterExt.hx b/flixel/effects/particles/FlxTypedEmitterExt.hx deleted file mode 100644 index 73d7bfbce8..0000000000 --- a/flixel/effects/particles/FlxTypedEmitterExt.hx +++ /dev/null @@ -1,272 +0,0 @@ -package flixel.effects.particles; - -import flixel.FlxSprite; -import flixel.math.FlxRandom; -import flixel.effects.particles.FlxParticle; - -/** - * Extended FlxEmitter that emits particles in a circle (instead of a square). - * It also provides a new function setMotion to control particle behavior even more. - * This was inspired by the way Chevy Ray Johnston implemented his particle emitter in Flashpunk. - * @author Dirk Bunk - */ -class FlxTypedEmitterExt extends FlxTypedEmitter -{ - /** - * Launch Direction. - */ - public var angle:Float; - /** - * Distance to travel. - */ - public var distance:Float; - /** - * Random amount to add to the particle's direction. - */ - public var angleRange:Float; - /** - * Random amount to add to the particle's distance. - */ - public var distanceRange:Float; - - /** - * Creates a new FlxTypedEmitterExt object at a specific position. - * Does NOT automatically generate or attach particles! - * - * @param X The X position of the emitter. - * @param Y The Y position of the emitter. - * @param Size Optional, specifies a maximum capacity for this emitter. - */ - public function new(X:Float = 0, Y:Float = 0, Size:Int = 0) - { - super(X, Y, Size); - - // Set defaults - setMotion(0, 0, 0.5, 360, 100, 1.5); - } - - /** - * Defines the motion range for this emitter. - * - * @param Angle Launch Direction. - * @param Distance Distance to travel. - * @param Lifespan 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 LifespanRange Random amount to add to the particle's duration. - */ - public function setMotion(Angle:Float, Distance:Float, Lifespan:Float, AngleRange:Float = 0, DistanceRange:Float = 0, LifespanRange:Float = 0):Void - { - angle = Angle * 0.017453293; - distance = Distance; - life.min = Lifespan; - life.max = Lifespan + LifespanRange; - angleRange = AngleRange * 0.017453293; - distanceRange = DistanceRange; - } - - /** - * Defines the motion range for a specific particle. - * - * @param Particle The Particle to set the motion for - * @param Angle Launch Direction. - * @param Distance Distance to travel. - * @param Lifespan 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 LifespanRange Random amount to add to the particle's duration. - */ - private function setParticleMotion(Particle:T, Angle:Float, Distance:Float, AngleRange:Float = 0, DistanceRange:Float = 0):Void - { - //set particle direction and speed - var a:Float = FlxRandom.floatRanged(Angle, Angle + AngleRange); - var d:Float = FlxRandom.floatRanged(Distance, Distance + DistanceRange); - - Particle.velocity.x = Math.cos(a) * d; - Particle.velocity.y = Math.sin(a) * d; - } - - /** - * Call this function to start emitting particles. - * - * @param Explode Whether the particles should all burst out at once. - * @param Lifespan Unused parameter due to class override. Use setMotion to set things like a particle's lifespan. - * @param Frequency Ignored if Explode is set to true. Frequency is how often to emit a particle. 0 = never emit, 0.1 = 1 particle every 0.1 seconds, 5 = 1 particle every 5 seconds. - * @param Quantity How many particles to launch. 0 = "all of the particles". - * @param LifespanRange Max amount to add to the particle's lifespan. Leave it to default (zero), if you want to make particle "live" forever (plus you should set Lifespan parameter to zero too). - */ - override public function start(Explode:Bool = true, Lifespan:Float = 0, Frequency:Float = 0.1, Quantity:Int = 0, LifespanRange:Float = 0):Void - { - super.start(Explode, Lifespan, Frequency, Quantity, LifespanRange); - - // Immediately execute the explosion code part from the update function, to prevent other explosions to override this one. - // This fixes the problem that you can not add two particle explosions in the same frame. - if (Explode) - { - emitting = false; - - var i:Int = 0; - var l:Int = _quantity; - - if ((l <= 0) || (l > members.length)) - { - l = members.length; - } - - while (i < l) - { - emitParticle(); - i++; - } - - _quantity = 0; - } - } - - /** - * This function can be used both internally and externally to emit the next particle. - */ - override public function emitParticle():Void - { - var particle:T = cast recycle(cast particleClass); - particle.elasticity = bounce; - - particle.reset(x - (Std.int(particle.width) >> 1) + FlxRandom.float() * width, y - (Std.int(particle.height) >> 1) + FlxRandom.float() * height); - particle.visible = true; - - if (life.min != life.max) - { - particle.lifespan = particle.maxLifespan = life.min + FlxRandom.float() * (life.max - life.min); - } - else - { - particle.lifespan = particle.maxLifespan = life.min; - } - - if (startAlpha.min != startAlpha.max) - { - particle.startAlpha = startAlpha.min + FlxRandom.float() * (startAlpha.max - startAlpha.min); - } - else - { - particle.startAlpha = startAlpha.min; - } - particle.alpha = particle.startAlpha; - - var particleEndAlpha:Float = endAlpha.min; - if (endAlpha.min != endAlpha.max) - { - particleEndAlpha = endAlpha.min + FlxRandom.float() * (endAlpha.max - endAlpha.min); - } - - if (particleEndAlpha != particle.startAlpha) - { - particle.useFading = true; - particle.rangeAlpha = particleEndAlpha - particle.startAlpha; - } - else - { - particle.useFading = false; - particle.rangeAlpha = 0; - } - - // particle color settings - var startRedComp:Float = particle.startRed = startRed.min; - var startGreenComp:Float = particle.startGreen = startGreen.min; - var startBlueComp:Float = particle.startBlue = startBlue.min; - - var endRedComp:Float = endRed.min; - var endGreenComp:Float = endGreen.min; - var endBlueComp:Float = endBlue.min; - - if (startRed.min != startRed.max) - { - particle.startRed = startRedComp = startRed.min + FlxRandom.float() * (startRed.max - startRed.min); - } - if (startGreen.min != startGreen.max) - { - particle.startGreen = startGreenComp = startGreen.min + FlxRandom.float() * (startGreen.max - startGreen.min); - } - if (startBlue.min != startBlue.max) - { - particle.startBlue = startBlueComp = startBlue.min + FlxRandom.float() * (startBlue.max - startBlue.min); - } - - if (endRed.min != endRed.max) - { - endRedComp = endRed.min + FlxRandom.float() * (endRed.max - endRed.min); - } - - if (endGreen.min != endGreen.max) - { - endGreenComp = endGreen.min + FlxRandom.float() * (endGreen.max - endGreen.min); - } - - if (endBlue.min != endBlue.max) - { - endBlueComp = endBlue.min + FlxRandom.float() * (endBlue.max - endBlue.min); - } - - particle.rangeRed = endRedComp - startRedComp; - particle.rangeGreen = endGreenComp - startGreenComp; - particle.rangeBlue = endBlueComp - startBlueComp; - - particle.useColoring = false; - - if (particle.rangeRed != 0 || particle.rangeGreen != 0 || particle.rangeBlue != 0) - { - particle.useColoring = true; - } - - // End of particle color settings - if (startScale.min != startScale.max) - { - particle.startScale = startScale.min + FlxRandom.float() * (startScale.max - startScale.min); - } - else - { - particle.startScale = startScale.min; - } - particle.scale.x = particle.scale.y = particle.startScale; - - var particleEndScale:Float = endScale.min; - - if (endScale.min != endScale.max) - { - particleEndScale = endScale.min + Std.int(FlxRandom.float() * (endScale.max - endScale.min)); - } - - if (particleEndScale != particle.startScale) - { - particle.useScaling = true; - particle.rangeScale = particleEndScale - particle.startScale; - } - else - { - particle.useScaling = false; - particle.rangeScale = 0; - } - - particle.blend = blend; - - // Set particle motion - setParticleMotion(particle, angle, distance, angleRange, distanceRange); - particle.acceleration.set(acceleration.x, acceleration.y); - - if (rotation.min != rotation.max) - { - particle.angularVelocity = rotation.min + FlxRandom.float() * (rotation.max - rotation.min); - } - else - { - particle.angularVelocity = rotation.min; - } - if (particle.angularVelocity != 0) - { - particle.angle = FlxRandom.float() * 360 - 180; - } - - particle.drag.set(particleDrag.x, particleDrag.y); - particle.onEmit(); - } -} \ No newline at end of file diff --git a/flixel/group/FlxGroup.hx b/flixel/group/FlxGroup.hx index 9d53ad5614..a06a6e8b3b 100644 --- a/flixel/group/FlxGroup.hx +++ b/flixel/group/FlxGroup.hx @@ -1,3 +1,831 @@ package flixel.group; -typedef FlxGroup = FlxTypedGroup; \ No newline at end of file +import flixel.FlxBasic; +import flixel.FlxG; +import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; +import flixel.util.FlxArrayUtil; +import flixel.util.FlxSort; + +typedef FlxGroup = FlxTypedGroup; + +/** + * This is an organizational class that can update and render a bunch of FlxBasics. + * NOTE: Although FlxGroup extends FlxBasic, it will not automatically + * add itself to the global collisions quad tree, it will only add its members. + */ +class FlxTypedGroup extends FlxBasic +{ + /** + * Helper function for overlap functions in FlxObject and FlxTilemap. + */ + @:allow(flixel.FlxObject) + @:allow(flixel.tile.FlxTilemap) + private static inline function overlaps(Callback:FlxBasic->Float->Float->Bool->FlxCamera->Bool, + Group:FlxTypedGroup, X:Float, Y:Float, InScreenSpace:Bool, Camera:FlxCamera):Bool + { + var result:Bool = false; + if (Group != null) + { + var i = 0; + var l = Group.length; + var basic:FlxBasic; + + while (i < l) + { + basic = cast Group.members[i++]; + + if (basic != null && Callback(basic, X, Y, InScreenSpace, Camera)) + { + result = true; + break; + } + } + } + return result; + } + + @:allow(flixel.FlxObject) + @:allow(flixel.tile.FlxTilemap) + @:allow(flixel.system.FlxQuadTree) + private static inline function resolveGroup(ObjectOrGroup:FlxBasic):FlxTypedGroup + { + var group:FlxTypedGroup = null; + if ((ObjectOrGroup.collisionType == SPRITEGROUP) || + (ObjectOrGroup.collisionType == GROUP)) + { + if (ObjectOrGroup.collisionType == GROUP) + { + group = cast ObjectOrGroup; + } + else if (ObjectOrGroup.collisionType == SPRITEGROUP) + { + group = cast cast(ObjectOrGroup, FlxTypedSpriteGroup).group; + } + } + return group; + } + + /** + * Array of all the members in this group. + */ + public var members(default, null):Array; + /** + * The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow. + */ + public var maxSize(default, set):Int; + /** + * The number of entries in the members array. For performance and safety you should check this + * variable instead of members.length unless you really know what you're doing! + */ + public var length(default, null):Int = 0; + /** + * Internal helper variable for recycling objects a la FlxEmitter. + */ + private var _marker:Int = 0; + + /** + * @param MaxSize Maximum amount of members allowed + */ + public function new(MaxSize:Int = 0) + { + super(); + + members = []; + + maxSize = Std.int(Math.abs(MaxSize)); + + collisionType = GROUP; + } + + /** + * WARNING: This will remove this group entirely. Use kill() if you want to disable it + * temporarily only and be able to revive() it later. + * Override this function to handle any deleting or "shutdown" type operations you might need, + * such as removing traditional Flash children like Sprite objects. + */ + override public function destroy():Void + { + super.destroy(); + + if (members != null) + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null) + basic.destroy(); + } + + members = null; + } + } + + /** + * Automatically goes through and calls update on everything you added. + */ + override public function update():Void + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && basic.exists && basic.active) + { + basic.update(); + } + } + } + + /** + * Automatically goes through and calls render on everything you added. + */ + override public function draw():Void + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && basic.exists && basic.visible) + { + basic.draw(); + } + } + } + + /** + * Adds a new FlxBasic subclass (FlxBasic, FlxSprite, Enemy, etc) to the group. + * FlxGroup will try to replace a null member of the array first. + * Failing that, FlxGroup will add it to the end of the member array, + * assuming there is room for it, and doubling the size of the array if necessary. + * WARNING: If the group has a maxSize that has already been met, + * the object will NOT be added to the group! + * + * @param Object The object you want to add to the group. + * @return The same FlxBasic object that was passed in. + */ + public function add(Object:T):T + { + if (Object == null) + { + FlxG.log.warn("Cannot add a `null` object to a FlxGroup."); + return null; + } + + // Don't bother adding an object twice. + if (members.indexOf(Object) >= 0) + { + return Object; + } + + // First, look for a null entry where we can add the object. + var index:Int = getFirstNull(); + if (index != -1) + { + members[index] = Object; + + if (index >= length) + { + length = index + 1; + } + + return Object; + } + + // If the group is full, return the Object + if (maxSize > 0 && length >= maxSize) + { + return Object; + } + + // If we made it this far, we need to add the object to the group. + members.push(Object); + length++; + + return Object; + } + + /** + * Recycling is designed to help you reuse game objects without always re-allocating or "newing" them. + * It behaves differently depending on whether maxSize equals 0 or is bigger than 0. + * + * maxSize > 0 / "rotating-recycling" (used by FlxEmitter): + * - at capacity: returns the next object in line, no matter its properties like alive, exists etc. + * - otherwise: returns a new object. + * + * maxSize == 0 / "grow-style-recycling" + * - tries to find the first object with exists == false + * - otherwise: adds a new object to the members array + * + * WARNING: If this function needs to create a new object, and no object class was provided, + * it will return null instead of a valid object! + * + * @param ObjectClass The class type you want to recycle (e.g. FlxSprite, EvilRobot, etc). Do NOT "new" the class in the parameter! + * @param ContructorArgs An array of arguments passed into a newly object if there aren't any dead members to recycle. + * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. + * @param Revive Whether recycled members should automatically be revived (by calling revive() on them) + * @return A reference to the object that was created. Don't forget to cast it back to the Class you want (e.g. myObject = myGroup.recycle(myObjectClass) as myObjectClass;). + */ + public function recycle(?ObjectClass:Class, ?ContructorArgs:Array, Force:Bool = false, Revive:Bool = true):T + { + if (ContructorArgs == null) + { + ContructorArgs = []; + } + + var basic:FlxBasic = null; + + // roatated recycling + if (maxSize > 0) + { + // create new instance + if (length < maxSize) + { + if (ObjectClass == null) + { + return null; + } + + return add(Type.createInstance(ObjectClass, ContructorArgs)); + } + // get the next member if at capacity + else + { + basic = members[_marker++]; + + if (_marker >= maxSize) + { + _marker = 0; + } + + if (Revive) + { + basic.revive(); + } + + return cast basic; + } + } + // grow-style recycling - grab a basic with exists == false or create a new one + else + { + basic = getFirstAvailable(ObjectClass, Force); + + if (basic != null) + { + if (Revive) + { + basic.revive(); + } + return cast basic; + } + if (ObjectClass == null) + { + return null; + } + + return add(Type.createInstance(ObjectClass, ContructorArgs)); + } + } + + /** + * Removes an object from the group. + * + * @param Object The FlxBasic you want to remove. + * @param Splice Whether the object should be cut from the array entirely or not. + * @return The removed object. + */ + public function remove(Object:T, Splice:Bool = false):T + { + if (members == null) + return null; + + var index:Int = members.indexOf(Object); + + if (index < 0) + return null; + + if (Splice) + members.splice(index, 1); + else + members[index] = null; + + return Object; + } + + /** + * Replaces an existing FlxBasic with a new one. + * Does not do anything and returns null if the old object is not part of the group. + * + * @param OldObject The object you want to replace. + * @param NewObject The new object you want to use instead. + * @return The new object. + */ + public function replace(OldObject:T, NewObject:T):T + { + var index:Int = members.indexOf(OldObject); + + if (index < 0) + return null; + + members[index] = NewObject; + + return NewObject; + } + + /** + * Call this function to sort the group according to a particular value and order. For example, to sort game objects for Zelda-style + * overlaps you might call myGroup.sort(FlxSort.byY, FlxSort.ASCENDING) at the bottom of your FlxState.update() override. + * + * @param Function The sorting function to use - you can use one of the premade ones in FlxSort or write your own using FlxSort.byValues() as a backend + * @param Order A FlxGroup constant that defines the sort order. Possible values are FlxSort.ASCENDING (default) and FlxSort.DESCENDING. + */ + public inline function sort(Function:Int->T->T->Int, Order:Int = FlxSort.ASCENDING):Void + { + members.sort(Function.bind(Order)); + } + + /** + * Go through and set the specified variable to the specified value on all members of the group. + * + * @param VariableName The string representation of the variable name you want to modify, for example "visible" or "scrollFactor". + * @param Value The value you want to assign to that variable. + * @param Recurse Default value is true, meaning if setAll() encounters a member that is a group, it will call setAll() on that group rather than modifying its variable. + */ + public function setAll(VariableName:String, Value:Dynamic, Recurse:Bool = true):Void + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null) + { + if (Recurse && basic.collisionType == GROUP) + { + (cast basic).setAll(VariableName, Value, Recurse); + } + else + { + Reflect.setProperty(basic, VariableName, Value); + } + } + } + } + + /** + * Go through and call the specified function on all members of the group, recursively by default. + * + * @param FunctionName The string representation of the function you want to call on each object, for example "kill()" or "init()". + * @param Args An array of arguments to call the function with + * @param Recurse Default value is true, meaning if callAll() encounters a member that is a group, it will call callAll() on that group rather than calling the group's function. + */ + public function callAll(FunctionName:String, ?Args:Array, Recurse:Bool = true):Void + { + if (Args == null) + Args = []; + + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null) + { + if (Recurse && (basic.collisionType == GROUP)) + { + (cast(basic, FlxTypedGroup)).callAll(FunctionName, Args, Recurse); + } + else + { + Reflect.callMethod(basic, Reflect.getProperty(basic, FunctionName), Args); + } + } + } + } + + /** + * Call this function to retrieve the first object with exists == false in the group. + * This is handy for recycling in general, e.g. respawning enemies. + * + * @param ObjectClass An optional parameter that lets you narrow the results to instances of this particular class. + * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. + * @return A FlxBasic currently flagged as not existing. + */ + public function getFirstAvailable(?ObjectClass:Class, Force:Bool = false):T + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; // we use basic as FlxBasic for performance reasons + + if ((basic != null) && !basic.exists && ((ObjectClass == null) || Std.is(basic, ObjectClass))) + { + if (Force && Type.getClassName(Type.getClass(basic)) != Type.getClassName(ObjectClass)) + { + continue; + } + return members[i - 1]; + } + } + + return null; + } + + /** + * Call this function to retrieve the first index set to 'null'. + * Returns -1 if no index stores a null object. + * + * @return An Int indicating the first null slot in the group. + */ + public function getFirstNull():Int + { + var i:Int = 0; + + while (i < length) + { + if (members[i] == null) + { + return i; + } + i++; + } + + return -1; + } + + /** + * Call this function to retrieve the first object with exists == true in the group. + * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. + * + * @return A FlxBasic currently flagged as existing. + */ + public function getFirstExisting():T + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && basic.exists) + { + return cast basic; + } + } + + return null; + } + + /** + * Call this function to retrieve the first object with dead == false in the group. + * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. + * + * @return A FlxBasic currently flagged as not dead. + */ + public function getFirstAlive():T + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; // we use basic as FlxBasic for performance reasons + + if (basic != null && basic.exists && basic.alive) + { + return cast basic; + } + } + + return null; + } + + /** + * Call this function to retrieve the first object with dead == true in the group. + * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. + * + * @return A FlxBasic currently flagged as dead. + */ + public function getFirstDead():T + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; // we use basic as FlxBasic for performance reasons + + if (basic != null && !basic.alive) + { + return cast basic; + } + } + + return null; + } + + /** + * Call this function to find out how many members of the group are not dead. + * + * @return The number of FlxBasics flagged as not dead. Returns -1 if group is empty. + */ + public function countLiving():Int + { + var i:Int = 0; + var count:Int = -1; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null) + { + if (count < 0) + { + count = 0; + } + if (basic.exists && basic.alive) + { + count++; + } + } + } + + return count; + } + + /** + * Call this function to find out how many members of the group are dead. + * + * @return The number of FlxBasics flagged as dead. Returns -1 if group is empty. + */ + public function countDead():Int + { + var i:Int = 0; + var count:Int = -1; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null) + { + if (count < 0) + { + count = 0; + } + if (!basic.alive) + { + count++; + } + } + } + + return count; + } + + /** + * Returns a member at random from the group. + * + * @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array. + * @param Length Optional restriction on the number of values you want to randomly select from. + * @return A FlxBasic from the members list. + */ + public function getRandom(StartIndex:Int = 0, Length:Int = 0):T + { + if (StartIndex < 0) + { + StartIndex = 0; + } + if (Length <= 0) + { + Length = length; + } + + return FlxArrayUtil.getRandom(members, StartIndex, Length); + } + + /** + * Remove all instances of FlxBasic subclass (FlxSprite, FlxBlock, etc) from the list. + * WARNING: does not destroy() or kill() any of these objects! + */ + public function clear():Void + { + length = 0; + FlxArrayUtil.clearArray(members); + } + + /** + * Calls kill on the group's members and then on the group itself. + * You can revive this group later via revive() after this. + */ + override public function kill():Void + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && basic.exists) + { + basic.kill(); + } + } + + super.kill(); + } + + /** + * Iterate through every member + * + * @return An iterator + */ + public inline function iterator(?filter:T->Bool):FlxTypedGroupIterator + { + return new FlxTypedGroupIterator(members, filter); + } + + /** + * Applies a function to all members + * + * @param Function A function that modifies one element at a time + */ + public function forEach(Function:T->Void) + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null) + Function(cast basic); + } + } + + /** + * Applies a function to all alive members + * + * @param Function A function that modifies one element at a time + */ + public function forEachAlive(Function:T->Void) + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && basic.exists && basic.alive) + Function(cast basic); + } + } + + /** + * Applies a function to all dead members + * + * @param Function A function that modifies one element at a time + */ + public function forEachDead(Function:T->Void) + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && !basic.alive) + Function(cast basic); + } + } + + /** + * Applies a function to all existing members + * + * @param Function A function that modifies one element at a time + */ + public function forEachExists(Function:T->Void) + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && basic.exists) + Function(cast basic); + } + } + + /** + * Applies a function to all members of type Class + * + * @param ObjectClass A class that objects will be checked against before Function is applied, ex: FlxSprite + * @param Function A function that modifies one element at a time + */ + public function forEachOfType(ObjectClass:Class, Function:K->Void) + { + var i:Int = 0; + var basic:FlxBasic = null; + + while (i < length) + { + basic = members[i++]; + + if (basic != null && Std.is(basic, ObjectClass)) + Function(cast basic); + } + } + + private function set_maxSize(Size:Int):Int + { + maxSize = Std.int(Math.abs(Size)); + + if (_marker >= maxSize) + { + _marker = 0; + } + if (maxSize == 0 || members == null || maxSize >= length) + { + return maxSize; + } + + // If the max size has shrunk, we need to get rid of some objects + var i:Int = maxSize; + var l:Int = length; + var basic:FlxBasic = null; + + while (i < l) + { + basic = members[i++]; + + if (basic != null) + basic.destroy(); + } + + FlxArrayUtil.setLength(members, maxSize); + length = members.length; + + return maxSize; + } +} + +/** + * Iterator implementation for groups + * Support a filter method (used for iteratorAlive, iteratorDead and iteratorExists) + * @author Masadow + */ +class FlxTypedGroupIterator +{ + private var _groupMembers:Array; + private var _filter:T->Bool; + private var _cursor:Int; + private var _length:Int; + + public function new(GroupMembers:Array, ?filter:T->Bool) + { + _groupMembers = GroupMembers; + _filter = filter; + _cursor = 0; + _length = _groupMembers.length; + } + + public function next() + { + return hasNext() ? _groupMembers[_cursor++] : null; + } + + public function hasNext():Bool + { + while (_cursor < _length && (_groupMembers[_cursor] == null || _filter != null && !_filter(_groupMembers[_cursor]))) + { + _cursor++; + } + return _cursor < _length; + } +} diff --git a/flixel/group/FlxSpriteGroup.hx b/flixel/group/FlxSpriteGroup.hx index 5c2bc31906..0ef477aff3 100644 --- a/flixel/group/FlxSpriteGroup.hx +++ b/flixel/group/FlxSpriteGroup.hx @@ -1,5 +1,1025 @@ package flixel.group; +import flash.display.BitmapData; +import flash.display.BlendMode; +import flixel.FlxCamera; +import flixel.FlxG; import flixel.FlxSprite; +import flixel.group.FlxGroup; +import flixel.math.FlxPoint; +import flixel.system.FlxAssets.FlxGraphicAsset; +import flixel.system.FlxAssets.FlxTextureAsset; +import flixel.system.layer.frames.FlxFrame; +import flixel.util.FlxDestroyUtil; +import flixel.util.FlxSort; -typedef FlxSpriteGroup = FlxTypedSpriteGroup; \ No newline at end of file +typedef FlxSpriteGroup = FlxTypedSpriteGroup; + +/** + * FlxSpriteGroup is a special FlxSprite that can be treated like + * a single sprite even if it's made up of several member sprites. + * It shares the FlxTypedGroup API, but it doesn't inherit from it. + */ +class FlxTypedSpriteGroup extends FlxSprite +{ + /** + * The actual group which holds all sprites + */ + public var group:FlxTypedGroup; + + /** + * The link to a group's members array + */ + public var members(get, null):Array; + + /** + * The number of entries in the members array. For performance and safety you should check this + * variable instead of members.length unless you really know what you're doing! + */ + public var length(get, null):Int; + + /** + * The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow. + */ + public var maxSize(get, set):Int; + + /** + * Optimization to allow setting position of group without transforming children twice. + */ + private var _skipTransformChildren:Bool = false; + + #if !FLX_NO_DEBUG + /** + * Just a helper variable to check if this group has already been drawn on debug layer + */ + private var _isDrawnDebug:Bool = false; + #end + + /** + * Array of all the FlxSprites that exist in this group for + * optimization purposes / static typing on cpp targets. + */ + private var _sprites:Array; + + /** + * @param X The initial X position of the group + * @param Y The initial Y position of the group + * @param MaxSize Maximum amount of members allowed + */ + public function new(X:Float = 0, Y:Float = 0, MaxSize:Int = 0) + { + super(X, Y); + group = new FlxTypedGroup(MaxSize); + _sprites = cast group.members; + } + + /** + * This method is used for initialization of variables of complex types. + * Don't forget to call super.initVars() if you'll override this method, + * or you'll get null object error and app will crash + */ + override private function initVars():Void + { + collisionType = SPRITEGROUP; + + offset = new FlxCallbackPoint(offsetCallback); + origin = new FlxCallbackPoint(originCallback); + scale = new FlxCallbackPoint(scaleCallback); + scrollFactor = new FlxCallbackPoint(scrollFactorCallback); + + scale.set(1, 1); + scrollFactor.set(1, 1); + + initMotionVars(); + } + + /** + * WARNING: This will remove this object entirely. Use kill() if you want to disable it temporarily only and reset() it later to revive it. + * Override this function to null out variables manually or call destroy() on class members if necessary. Don't forget to call super.destroy()! + */ + override public function destroy():Void + { + // normally don't have to destroy FlxPoints, but these are FlxCallbackPoints! + offset = FlxDestroyUtil.destroy(offset); + origin = FlxDestroyUtil.destroy(origin); + scale = FlxDestroyUtil.destroy(scale); + scrollFactor = FlxDestroyUtil.destroy(scrollFactor); + + group = FlxDestroyUtil.destroy(group); + _sprites = null; + + super.destroy(); + } + + /** + * Recursive cloning method: it will create copy of this group which will hold copies of all sprites + * + * @param NewSprite optional sprite group to copy to + * @return copy of this sprite group + */ + override public function clone(?NewSprite:FlxSprite):FlxTypedSpriteGroup + { + if (NewSprite == null || !Std.is(NewSprite, FlxTypedSpriteGroup)) + { + NewSprite = new FlxTypedSpriteGroup(0, 0, group.maxSize); + } + + var cloned:FlxTypedSpriteGroup = cast NewSprite; + cloned.maxSize = group.maxSize; + + for (sprite in _sprites) + { + if (sprite != null) + { + cloned.add(cast sprite.clone()); + } + } + return cloned; + } + + /** + * Check and see if any sprite in this group is currently on screen. + * + * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera. + * @return Whether the object is on screen or not. + */ + override public function isOnScreen(?Camera:FlxCamera):Bool + { + var result:Bool = false; + for (sprite in _sprites) + { + if (sprite != null && sprite.exists && sprite.visible) + { + result = result || sprite.isOnScreen(Camera); + } + } + + return result; + } + + /** + * Checks to see if a point in 2D world space overlaps any FlxSprite object from this group. + * + * @param Point The point in world space you want to check. + * @param InScreenSpace Whether to take scroll factors into account when checking for overlap. + * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera. + * @return Whether or not the point overlaps this group. + */ + override public function overlapsPoint(point:FlxPoint, InScreenSpace:Bool = false, ?Camera:FlxCamera):Bool + { + var result:Bool = false; + for (sprite in _sprites) + { + if ((sprite != null) && sprite.exists && sprite.visible) + { + result = result || sprite.overlapsPoint(point, InScreenSpace, Camera); + } + } + + return result; + } + + /** + * Checks to see if a point in 2D world space overlaps any of FlxSprite object's current displayed pixels. + * This check is ALWAYS made in screen space, and always takes scroll factors into account. + * + * @param Point The point in world space you want to check. + * @param Mask Used in the pixel hit test to determine what counts as solid. + * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera. + * @return Whether or not the point overlaps this object. + */ + override public function pixelsOverlapPoint(point:FlxPoint, Mask:Int = 0xFF, ?Camera:FlxCamera):Bool + { + var result:Bool = false; + for (sprite in _sprites) + { + if ((sprite != null) && sprite.exists && sprite.visible) + { + result = result || sprite.pixelsOverlapPoint(point, Mask, Camera); + } + } + + return result; + } + + override public function update():Void + { + group.update(); + + if (moves) + { + updateMotion(); + } + } + + override public function draw():Void + { + group.draw(); + #if !FLX_NO_DEBUG + _isDrawnDebug = false; + #end + } + + /** + * Replaces all pixels with specified Color with NewColor pixels. This operation is applied to every nested sprite from this group + * + * @param Color Color to replace + * @param NewColor New color + * @param FetchPositions Whether we need to store positions of pixels which colors were replaced + * @return Array replaced pixels positions + */ + override public function replaceColor(Color:Int, NewColor:Int, FetchPositions:Bool = false):Array + { + var positions:Array = null; + if (FetchPositions) + { + positions = new Array(); + } + + var spritePositions:Array; + for (sprite in _sprites) + { + if (sprite != null) + { + spritePositions = sprite.replaceColor(Color, NewColor, FetchPositions); + if (FetchPositions) + { + positions = positions.concat(spritePositions); + } + } + } + + return positions; + } + + /** + * Adds a new FlxSprite subclass to the group. + * + * @param Object The sprite or sprite group you want to add to the group. + * @return The same object that was passed in. + */ + public function add(Sprite:T):T + { + var sprite:FlxSprite = cast Sprite; + sprite.x += x; + sprite.y += y; + sprite.alpha *= alpha; + sprite.scrollFactor.copyFrom(scrollFactor); + sprite.cameras = _cameras; // _cameras instead of cameras because get_cameras() will not return null + return group.add(Sprite); + } + + /** + * Recycling is designed to help you reuse game objects without always re-allocating or "newing" them. + * + * @param ObjectClass The class type you want to recycle (e.g. FlxSprite, EvilRobot, etc). Do NOT "new" the class in the parameter! + * @param ContructorArgs An array of arguments passed into a newly object if there aren't any dead members to recycle. + * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. + * @return A reference to the object that was created. Don't forget to cast it back to the Class you want (e.g. myObject = myGroup.recycle(myObjectClass) as myObjectClass;). + */ + public inline function recycle(?ObjectClass:Class, ?ContructorArgs:Array, Force:Bool = false):FlxSprite + { + return group.recycle(ObjectClass, ContructorArgs, Force); + } + + /** + * Removes specified sprite from the group. + * + * @param Object The FlxSprite you want to remove. + * @param Splice Whether the object should be cut from the array entirely or not. + * @return The removed object. + */ + public function remove(Object:T, Splice:Bool = false):T + { + return group.remove(Object, Splice); + } + + /** + * Replaces an existing FlxSprite with a new one. + * + * @param OldObject The object you want to replace. + * @param NewObject The new object you want to use instead. + * @return The new object. + */ + public inline function replace(OldObject:T, NewObject:T):T + { + return group.replace(OldObject, NewObject); + } + + /** + * Call this function to sort the group according to a particular value and order. For example, to sort game objects for Zelda-style + * overlaps you might call myGroup.sort(FlxSort.byY, FlxSort.ASCENDING) at the bottom of your FlxState.update() override. + * + * @param Function The sorting function to use - you can use one of the premade ones in FlxSort or write your own using FlxSort.byValues() as a backend + * @param Order A FlxGroup constant that defines the sort order. Possible values are FlxSort.ASCENDING (default) and FlxSort.DESCENDING. + */ + public inline function sort(Function:Int->T->T->Int, Order:Int = FlxSort.ASCENDING):Void + { + group.sort(Function, Order); + } + + /** + * Go through and set the specified variable to the specified value on all members of the group. + * + * @param VariableName The string representation of the variable name you want to modify, for example "visible" or "scrollFactor". + * @param Value The value you want to assign to that variable. + * @param Recurse Default value is true, meaning if setAll() encounters a member that is a group, it will call setAll() on that group rather than modifying its variable. + */ + public inline function setAll(VariableName:String, Value:Dynamic, Recurse:Bool = true):Void + { + group.setAll(VariableName, Value, Recurse); + } + + /** + * Go through and call the specified function on all members of the group. + * Currently only works on functions that have no required parameters. + * + * @param FunctionName The string representation of the function you want to call on each object, for example "kill()" or "init()". + * @param Recurse Default value is true, meaning if callAll() encounters a member that is a group, it will call callAll() on that group rather than calling the group's function. + */ + public inline function callAll(FunctionName:String, ?Args:Array, Recurse:Bool = true):Void + { + group.callAll(FunctionName, Args, Recurse); + } + + /** + * Call this function to retrieve the first object with exists == false in the group. + * This is handy for recycling in general, e.g. respawning enemies. + * + * @param ObjectClass An optional parameter that lets you narrow the results to instances of this particular class. + * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. + * @return A FlxSprite currently flagged as not existing. + */ + public inline function getFirstAvailable(?ObjectClass:Class, Force:Bool = false):T + { + return group.getFirstAvailable(ObjectClass, Force); + } + + /** + * Call this function to retrieve the first index set to 'null'. + * Returns -1 if no index stores a null object. + * + * @return An Int indicating the first null slot in the group. + */ + public inline function getFirstNull():Int + { + return group.getFirstNull(); + } + + /** + * Call this function to retrieve the first object with exists == true in the group. + * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. + * + * @return A FlxSprite currently flagged as existing. + */ + public inline function getFirstExisting():T + { + return group.getFirstExisting(); + } + + /** + * Call this function to retrieve the first object with dead == false in the group. + * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. + * + * @return A FlxSprite currently flagged as not dead. + */ + public inline function getFirstAlive():T + { + return group.getFirstAlive(); + } + + /** + * Call this function to retrieve the first object with dead == true in the group. + * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. + * + * @return A FlxSprite currently flagged as dead. + */ + public inline function getFirstDead():T + { + return group.getFirstDead(); + } + + /** + * Call this function to find out how many members of the group are not dead. + * + * @return The number of FlxSprites flagged as not dead. Returns -1 if group is empty. + */ + public inline function countLiving():Int + { + return group.countLiving(); + } + + /** + * Call this function to find out how many members of the group are dead. + * + * @return The number of FlxSprites flagged as dead. Returns -1 if group is empty. + */ + public inline function countDead():Int + { + return group.countDead(); + } + + /** + * Returns a member at random from the group. + * + * @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array. + * @param Length Optional restriction on the number of values you want to randomly select from. + * @return A FlxSprite from the members list. + */ + public inline function getRandom(StartIndex:Int = 0, Length:Int = 0):T + { + return group.getRandom(StartIndex, Length); + } + + /** + * Iterate through every member + * + * @return An iterator + */ + public inline function iterator(?filter:T->Bool):FlxTypedGroupIterator + { + return new FlxTypedGroupIterator(members, filter); + } + + /** + * Applies a function to all members + * + * @param Function A function that modifies one element at a time + */ + public inline function forEach(Function:T->Void):Void + { + group.forEach(Function); + } + + /** + * Applies a function to all alive members + * + * @param Function A function that modifies one element at a time + */ + public inline function forEachAlive(Function:T->Void):Void + { + group.forEachAlive(Function); + } + + /** + * Applies a function to all dead members + * + * @param Function A function that modifies one element at a time + */ + public inline function forEachDead(Function:T->Void):Void + { + group.forEachDead(Function); + } + + /** + * Applies a function to all existing members + * + * @param Function A function that modifies one element at a time + */ + public inline function forEachExists(Function:T->Void):Void + { + group.forEachExists(Function); + } + + /** + * Applies a function to all members of type Class + * + * @param ObjectClass A class that objects will be checked against before Function is applied, ex: FlxSprite + * @param Function A function that modifies one element at a time + */ + public inline function forEachOfType(ObjectClass:Class, Function:K->Void) + { + group.forEachOfType(ObjectClass, Function); + } + + /** + * Remove all instances of FlxSprite from the list. + * WARNING: does not destroy() or kill() any of these objects! + */ + public inline function clear():Void + { + group.clear(); + } + + /** + * Calls kill on the group's members and then on the group itself. + * You can revive this group later via revive() after this. + */ + override public function kill():Void + { + super.kill(); + group.kill(); + } + + /** + * Revives the group. + */ + override public function revive():Void + { + super.revive(); + group.revive(); + } + + /** + * Helper function for the sort process. + * + * @param Obj1 The first object being sorted. + * @param Obj2 The second object being sorted. + * @return An integer value: -1 (Obj1 before Obj2), 0 (same), or 1 (Obj1 after Obj2). + */ + override public function reset(X:Float, Y:Float):Void + { + revive(); + setPosition(X, Y); + + for (sprite in _sprites) + { + if (sprite != null) + { + sprite.reset(X, Y); + } + } + } + + /** + * Helper function to set the coordinates of this object. + * Handy since it only requires one line of code. + * + * @param X The new x position + * @param Y The new y position + */ + override public function setPosition(X:Float = 0, Y:Float = 0):Void + { + // Transform children by the movement delta + var dx:Float = X - x; + var dy:Float = Y - y; + multiTransformChildren([xTransform, yTransform], [dx, dy]); + + // don't transform children twice + _skipTransformChildren = true; + x = X; // this calls set_x + y = Y; // this calls set_y + _skipTransformChildren = false; + } + + /** + * Handy function that allows you to quickly transform one property of sprites in this group at a time. + * + * @param Function Function to transform the sprites. Example: function(s:FlxSprite, v:Dynamic) { s.acceleration.x = v; s.makeGraphic(10,10,0xFF000000); } + * @param Value Value which will passed to lambda function + */ + @:generic + public function transformChildren(Function:T->V->Void, Value:V):Void + { + if (group == null) + { + return; + } + + for (sprite in _sprites) + { + if ((sprite != null) && sprite.exists) + { + Function(cast sprite, Value); + } + } + } + + /** + * Handy function that allows you to quickly transform multiple properties of sprites in this group at a time. + * + * @param FunctionArray Array of functions to transform sprites in this group. + * @param ValueArray Array of values which will be passed to lambda functions + */ + @:generic + public function multiTransformChildren(FunctionArray:ArrayV->Void>, ValueArray:Array):Void + { + if (group == null) + { + return; + } + + var numProps:Int = FunctionArray.length; + if (numProps > ValueArray.length) + { + return; + } + + var lambda:T->V->Void; + for (sprite in _sprites) + { + if ((sprite != null) && sprite.exists) + { + for (i in 0...numProps) + { + lambda = FunctionArray[i]; + lambda(cast sprite, ValueArray[i]); + } + } + } + } + + // PROPERTIES GETTERS/SETTERS + + override private function set_cameras(Value:Array):Array + { + if (cameras != Value) + transformChildren(camerasTransform, Value); + return super.set_cameras(Value); + } + + override private function set_exists(Value:Bool):Bool + { + if (exists != Value) + transformChildren(existsTransform, Value); + return super.set_exists(Value); + } + + override private function set_visible(Value:Bool):Bool + { + if (exists && visible != Value) + transformChildren(visibleTransform, Value); + return super.set_visible(Value); + } + + override private function set_active(Value:Bool):Bool + { + if (exists && active != Value) + transformChildren(activeTransform, Value); + return super.set_active(Value); + } + + override private function set_alive(Value:Bool):Bool + { + if (exists && alive != Value) + transformChildren(aliveTransform, Value); + return super.set_alive(Value); + } + + override private function set_x(Value:Float):Float + { + if (!_skipTransformChildren && exists && x != Value) + { + var offset:Float = Value - x; + transformChildren(xTransform, offset); + } + + return x = Value; + } + + override private function set_y(Value:Float):Float + { + if (!_skipTransformChildren && exists && y != Value) + { + var offset:Float = Value - y; + transformChildren(yTransform, offset); + } + + return y = Value; + } + + override private function set_angle(Value:Float):Float + { + if (exists && angle != Value) + { + var offset:Float = Value - angle; + transformChildren(angleTransform, offset); + } + return angle = Value; + } + + override private function set_alpha(Value:Float):Float + { + if (Value > 1) + { + Value = 1; + } + else if (Value < 0) + { + Value = 0; + } + + if (exists && alpha != Value) + { + var factor:Float = (alpha > 0) ? Value / alpha : 0; + transformChildren(alphaTransform, factor); + } + return alpha = Value; + } + + override private function set_facing(Value:Int):Int + { + if (exists && facing != Value) + transformChildren(facingTransform, Value); + return facing = Value; + } + + override private function set_flipX(Value:Bool):Bool + { + if (exists && flipX != Value) + transformChildren(flipXTransform, Value); + return flipX = Value; + } + + override private function set_flipY(Value:Bool):Bool + { + if (exists && flipY != Value) + transformChildren(flipYTransform, Value); + return flipY = Value; + } + + override private function set_moves(Value:Bool):Bool + { + if (exists && moves != Value) + transformChildren(movesTransform, Value); + return moves = Value; + } + + override private function set_immovable(Value:Bool):Bool + { + if (exists && immovable != Value) + transformChildren(immovableTransform, Value); + return immovable = Value; + } + + override private function set_solid(Value:Bool):Bool + { + if (exists && solid != Value) + transformChildren(solidTransform, Value); + return super.set_solid(Value); + } + + override private function set_color(Value:Int):Int + { + if (exists && color != Value) + transformChildren(gColorTransform, Value); + return color = Value; + } + + override private function set_blend(Value:BlendMode):BlendMode + { + if (exists && blend != Value) + transformChildren(blendTransform, Value); + return blend = Value; + } + + override private function set_pixelPerfectRender(Value:Bool):Bool + { + if (exists && pixelPerfectRender != Value) + transformChildren(pixelPerfectTransform, Value); + return super.set_pixelPerfectRender(Value); + } + + /** + * This functionality isn't supported in SpriteGroup + */ + override private function set_width(Value:Float):Float + { + return Value; + } + + override private function get_width():Float + { + if (length == 0) + { + return 0; + } + + var minX:Float = Math.POSITIVE_INFINITY; + var maxX:Float = Math.NEGATIVE_INFINITY; + + for (member in _sprites) + { + var minMemberX:Float = member.x; + var maxMemberX:Float = minMemberX + member.width; + + if (maxMemberX > maxX) + { + maxX = maxMemberX; + } + if (minMemberX < minX) + { + minX = minMemberX; + } + } + return (maxX - minX); + } + + /** + * This functionality isn't supported in SpriteGroup + */ + override private function set_height(Value:Float):Float + { + return Value; + } + + override private function get_height():Float + { + if (length == 0) + { + return 0; + } + + var minY:Float = Math.POSITIVE_INFINITY; + var maxY:Float = Math.NEGATIVE_INFINITY; + + for (member in _sprites) + { + var minMemberY:Float = member.y; + var maxMemberY:Float = minMemberY + member.height; + + if (maxMemberY > maxY) + { + maxY = maxMemberY; + } + if (minMemberY < minY) + { + minY = minMemberY; + } + } + return (maxY - minY); + } + + // GROUP FUNCTIONS + + private inline function get_length():Int + { + return group.length; + } + + private inline function get_maxSize():Int + { + return group.maxSize; + } + + private inline function set_maxSize(Size:Int):Int + { + return group.maxSize = Size; + } + + private inline function get_members():Array + { + return group.members; + } + + // TRANSFORM FUNCTIONS - STATIC TYPING + + private inline function xTransform(Sprite:FlxSprite, X:Float) { Sprite.x += X; } // addition + private inline function yTransform(Sprite:FlxSprite, Y:Float) { Sprite.y += Y; } // addition + private inline function angleTransform(Sprite:FlxSprite, Angle:Float) { Sprite.angle += Angle; } // addition + private inline function alphaTransform(Sprite:FlxSprite, Alpha:Float) { Sprite.alpha *= Alpha; } // multiplication + private inline function facingTransform(Sprite:FlxSprite, Facing:Int) { Sprite.facing = Facing; } // set + private inline function flipXTransform(Sprite:FlxSprite, FlipX:Bool) { Sprite.flipX = FlipX; } // set + private inline function flipYTransform(Sprite:FlxSprite, FlipY:Bool) { Sprite.flipY = FlipY; } // set + private inline function movesTransform(Sprite:FlxSprite, Moves:Bool) { Sprite.moves = Moves; } // set + private inline function pixelPerfectTransform(Sprite:FlxSprite, PixelPerfect:Bool) { Sprite.pixelPerfectRender = PixelPerfect; } // set + private inline function gColorTransform(Sprite:FlxSprite, Color:Int) { Sprite.color = Color; } // set + private inline function blendTransform(Sprite:FlxSprite, Blend:BlendMode) { Sprite.blend = Blend; } // set + private inline function immovableTransform(Sprite:FlxSprite, Immovable:Bool) { Sprite.immovable = Immovable; } // set + private inline function visibleTransform(Sprite:FlxSprite, Visible:Bool) { Sprite.visible = Visible; } // set + private inline function activeTransform(Sprite:FlxSprite, Active:Bool) { Sprite.active = Active; } // set + private inline function solidTransform(Sprite:FlxSprite, Solid:Bool) { Sprite.solid = Solid; } // set + private inline function aliveTransform(Sprite:FlxSprite, Alive:Bool) { Sprite.alive = Alive; } // set + private inline function existsTransform(Sprite:FlxSprite, Exists:Bool) { Sprite.exists = Exists; } // set + private inline function camerasTransform(Sprite:FlxSprite, Cameras:Array) { Sprite.cameras = Cameras; } // set + + private inline function offsetTransform(Sprite:FlxSprite, Offset:FlxPoint) { Sprite.offset.copyFrom(Offset); } // set + private inline function originTransform(Sprite:FlxSprite, Origin:FlxPoint) { Sprite.origin.copyFrom(Origin); } // set + private inline function scaleTransform(Sprite:FlxSprite, Scale:FlxPoint) { Sprite.scale.copyFrom(Scale); } // set + private inline function scrollFactorTransform(Sprite:FlxSprite, ScrollFactor:FlxPoint) { Sprite.scrollFactor.copyFrom(ScrollFactor); } // set + + // Functions for the FlxCallbackPoint + private inline function offsetCallback(Offset:FlxPoint) { transformChildren(offsetTransform, Offset); } + private inline function originCallback(Origin:FlxPoint) { transformChildren(originTransform, Origin); } + private inline function scaleCallback(Scale:FlxPoint) { transformChildren(scaleTransform, Scale); } + private inline function scrollFactorCallback(ScrollFactor:FlxPoint) { transformChildren(scrollFactorTransform, ScrollFactor); } + + // NON-SUPPORTED FUNCTIONALITY + // THESE METHODS ARE OVERRIDEN FOR SAFETY PURPOSES + + /** + * This functionality isn't supported in SpriteGroup + * @return this sprite group + */ + override public function loadGraphicFromSprite(Sprite:FlxSprite):FlxSprite + { + #if !FLX_NO_DEBUG + FlxG.log.error("loadGraphicFromSprite() is not supported in FlxSpriteGroups."); + #end + return this; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return this sprite group + */ + override public function loadGraphic(Graphic:FlxGraphicAsset, Animated:Bool = false, Width:Int = 0, Height:Int = 0, Unique:Bool = false, ?Key:String):FlxSprite + { + return this; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return this sprite group + */ + override public function loadRotatedGraphic(Graphic:FlxGraphicAsset, Rotations:Int = 16, Frame:Int = -1, AntiAliasing:Bool = false, AutoBuffer:Bool = false, ?Key:String):FlxSprite + { + #if !FLX_NO_DEBUG + FlxG.log.error("loadRotatedGraphic() is not supported in FlxSpriteGroups."); + #end + return this; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return this sprite group + */ + override public function makeGraphic(Width:Int, Height:Int, Color:Int = 0xffffffff, Unique:Bool = false, ?Key:String):FlxSprite + { + #if !FLX_NO_DEBUG + FlxG.log.error("makeGraphic() is not supported in FlxSpriteGroups."); + #end + return this; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return this sprite group + */ + override public function loadGraphicFromTexture(Data:FlxTextureAsset, Unique:Bool = false, ?FrameName:String):FlxSprite + { + #if !FLX_NO_DEBUG + FlxG.log.error("loadGraphicFromTexture() is not supported in FlxSpriteGroups."); + #end + return this; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return this sprite group + */ + override public function loadRotatedGraphicFromTexture(Data:Dynamic, Image:String, Rotations:Int = 16, AntiAliasing:Bool = false, AutoBuffer:Bool = false):FlxSprite + { + #if !FLX_NO_DEBUG + FlxG.log.error("loadRotatedGraphicFromTexture() is not supported in FlxSpriteGroups."); + #end + return this; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return the BitmapData passed in as parameter + */ + override private function set_pixels(Value:BitmapData):BitmapData + { + return Value; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return the FlxFrame passed in as parameter + */ + override private function set_frame(Value:FlxFrame):FlxFrame + { + return Value; + } + + /** + * This functionality isn't supported in SpriteGroup + * @return WARNING: returns null + */ + override private function get_pixels():BitmapData + { + return null; + } + + /** + * Internal function to update the current animation frame. + * + * @param RunOnCpp Whether the frame should also be recalculated if we're on a non-flash target + */ + override private inline function calcFrame(RunOnCpp:Bool = false):Void + { + // Nothing to do here + } + + /** + * This functionality isn't supported in SpriteGroup + */ + override private inline function resetHelpers():Void {} + + /** + * This functionality isn't supported in SpriteGroup + */ + override public inline function stamp(Brush:FlxSprite, X:Int = 0, Y:Int = 0):Void {} + + /** + * This functionality isn't supported in SpriteGroup + */ + override private inline function updateColorTransform():Void {} + + /** + * This functionality isn't supported in SpriteGroup + */ + override public inline function updateFrameData():Void {} +} \ No newline at end of file diff --git a/flixel/group/FlxTypedGroup.hx b/flixel/group/FlxTypedGroup.hx deleted file mode 100644 index 5646ffd316..0000000000 --- a/flixel/group/FlxTypedGroup.hx +++ /dev/null @@ -1,828 +0,0 @@ -package flixel.group; - -import flixel.FlxBasic; -import flixel.FlxG; -import flixel.util.FlxArrayUtil; -import flixel.util.FlxSort; - -/** - * This is an organizational class that can update and render a bunch of FlxBasics. - * NOTE: Although FlxGroup extends FlxBasic, it will not automatically - * add itself to the global collisions quad tree, it will only add its members. - */ -class FlxTypedGroup extends FlxBasic -{ - /** - * Helper function for overlap functions in FlxObject and FlxTilemap. - */ - @:allow(flixel.FlxObject) - @:allow(flixel.tile.FlxTilemap) - private static inline function overlaps(Callback:FlxBasic->Float->Float->Bool->FlxCamera->Bool, - Group:FlxTypedGroup, X:Float, Y:Float, InScreenSpace:Bool, Camera:FlxCamera):Bool - { - var result:Bool = false; - if (Group != null) - { - var i = 0; - var l = Group.length; - var basic:FlxBasic; - - while (i < l) - { - basic = cast Group.members[i++]; - - if (basic != null && Callback(basic, X, Y, InScreenSpace, Camera)) - { - result = true; - break; - } - } - } - return result; - } - - @:allow(flixel.FlxObject) - @:allow(flixel.tile.FlxTilemap) - @:allow(flixel.system.FlxQuadTree) - private static inline function resolveGroup(ObjectOrGroup:FlxBasic):FlxTypedGroup - { - var group:FlxTypedGroup = null; - if ((ObjectOrGroup.collisionType == SPRITEGROUP) || - (ObjectOrGroup.collisionType == GROUP)) - { - if (ObjectOrGroup.collisionType == GROUP) - { - group = cast ObjectOrGroup; - } - else if (ObjectOrGroup.collisionType == SPRITEGROUP) - { - group = cast cast(ObjectOrGroup, FlxTypedSpriteGroup).group; - } - } - return group; - } - - /** - * Array of all the members in this group. - */ - public var members(default, null):Array; - /** - * The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow. - */ - public var maxSize(default, set):Int; - /** - * The number of entries in the members array. For performance and safety you should check this - * variable instead of members.length unless you really know what you're doing! - */ - public var length(default, null):Int = 0; - /** - * Internal helper variable for recycling objects a la FlxEmitter. - */ - private var _marker:Int = 0; - - /** - * @param MaxSize Maximum amount of members allowed - */ - public function new(MaxSize:Int = 0) - { - super(); - - members = []; - - maxSize = Std.int(Math.abs(MaxSize)); - - collisionType = GROUP; - } - - /** - * WARNING: This will remove this group entirely. Use kill() if you want to disable it - * temporarily only and be able to revive() it later. - * Override this function to handle any deleting or "shutdown" type operations you might need, - * such as removing traditional Flash children like Sprite objects. - */ - override public function destroy():Void - { - super.destroy(); - - if (members != null) - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null) - basic.destroy(); - } - - members = null; - } - } - - /** - * Automatically goes through and calls update on everything you added. - */ - override public function update():Void - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && basic.exists && basic.active) - { - basic.update(); - } - } - } - - /** - * Automatically goes through and calls render on everything you added. - */ - override public function draw():Void - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && basic.exists && basic.visible) - { - basic.draw(); - } - } - } - - /** - * Adds a new FlxBasic subclass (FlxBasic, FlxSprite, Enemy, etc) to the group. - * FlxGroup will try to replace a null member of the array first. - * Failing that, FlxGroup will add it to the end of the member array, - * assuming there is room for it, and doubling the size of the array if necessary. - * WARNING: If the group has a maxSize that has already been met, - * the object will NOT be added to the group! - * - * @param Object The object you want to add to the group. - * @return The same FlxBasic object that was passed in. - */ - public function add(Object:T):T - { - if (Object == null) - { - FlxG.log.warn("Cannot add a `null` object to a FlxGroup."); - return null; - } - - // Don't bother adding an object twice. - if (members.indexOf(Object) >= 0) - { - return Object; - } - - // First, look for a null entry where we can add the object. - var index:Int = getFirstNull(); - if (index != -1) - { - members[index] = Object; - - if (index >= length) - { - length = index + 1; - } - - return Object; - } - - // If the group is full, return the Object - if (maxSize > 0 && length >= maxSize) - { - return Object; - } - - // If we made it this far, we need to add the object to the group. - members.push(Object); - length++; - - return Object; - } - - /** - * Recycling is designed to help you reuse game objects without always re-allocating or "newing" them. - * It behaves differently depending on whether maxSize equals 0 or is bigger than 0. - * - * maxSize > 0 / "rotating-recycling" (used by FlxEmitter): - * - at capacity: returns the next object in line, no matter its properties like alive, exists etc. - * - otherwise: returns a new object. - * - * maxSize == 0 / "grow-style-recycling" - * - tries to find the first object with exists == false - * - otherwise: adds a new object to the members array - * - * WARNING: If this function needs to create a new object, and no object class was provided, - * it will return null instead of a valid object! - * - * @param ObjectClass The class type you want to recycle (e.g. FlxSprite, EvilRobot, etc). Do NOT "new" the class in the parameter! - * @param ContructorArgs An array of arguments passed into a newly object if there aren't any dead members to recycle. - * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. - * @param Revive Whether recycled members should automatically be revived (by calling revive() on them) - * @return A reference to the object that was created. Don't forget to cast it back to the Class you want (e.g. myObject = myGroup.recycle(myObjectClass) as myObjectClass;). - */ - public function recycle(?ObjectClass:Class, ?ContructorArgs:Array, Force:Bool = false, Revive:Bool = true):T - { - if (ContructorArgs == null) - { - ContructorArgs = []; - } - - var basic:FlxBasic = null; - - // roatated recycling - if (maxSize > 0) - { - // create new instance - if (length < maxSize) - { - if (ObjectClass == null) - { - return null; - } - - return add(Type.createInstance(ObjectClass, ContructorArgs)); - } - // get the next member if at capacity - else - { - basic = members[_marker++]; - - if (_marker >= maxSize) - { - _marker = 0; - } - - if (Revive) - { - basic.revive(); - } - - return cast basic; - } - } - // grow-style recycling - grab a basic with exists == false or create a new one - else - { - basic = getFirstAvailable(ObjectClass, Force); - - if (basic != null) - { - if (Revive) - { - basic.revive(); - } - return cast basic; - } - if (ObjectClass == null) - { - return null; - } - - return add(Type.createInstance(ObjectClass, ContructorArgs)); - } - } - - /** - * Removes an object from the group. - * - * @param Object The FlxBasic you want to remove. - * @param Splice Whether the object should be cut from the array entirely or not. - * @return The removed object. - */ - public function remove(Object:T, Splice:Bool = false):T - { - if (members == null) - return null; - - var index:Int = members.indexOf(Object); - - if (index < 0) - return null; - - if (Splice) - members.splice(index, 1); - else - members[index] = null; - - return Object; - } - - /** - * Replaces an existing FlxBasic with a new one. - * Does not do anything and returns null if the old object is not part of the group. - * - * @param OldObject The object you want to replace. - * @param NewObject The new object you want to use instead. - * @return The new object. - */ - public function replace(OldObject:T, NewObject:T):T - { - var index:Int = members.indexOf(OldObject); - - if (index < 0) - return null; - - members[index] = NewObject; - - return NewObject; - } - - /** - * Call this function to sort the group according to a particular value and order. For example, to sort game objects for Zelda-style - * overlaps you might call myGroup.sort(FlxSort.byY, FlxSort.ASCENDING) at the bottom of your FlxState.update() override. - * - * @param Function The sorting function to use - you can use one of the premade ones in FlxSort or write your own using FlxSort.byValues() as a backend - * @param Order A FlxGroup constant that defines the sort order. Possible values are FlxSort.ASCENDING (default) and FlxSort.DESCENDING. - */ - public inline function sort(Function:Int->T->T->Int, Order:Int = FlxSort.ASCENDING):Void - { - members.sort(Function.bind(Order)); - } - - /** - * Go through and set the specified variable to the specified value on all members of the group. - * - * @param VariableName The string representation of the variable name you want to modify, for example "visible" or "scrollFactor". - * @param Value The value you want to assign to that variable. - * @param Recurse Default value is true, meaning if setAll() encounters a member that is a group, it will call setAll() on that group rather than modifying its variable. - */ - public function setAll(VariableName:String, Value:Dynamic, Recurse:Bool = true):Void - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null) - { - if (Recurse && basic.collisionType == GROUP) - { - (cast basic).setAll(VariableName, Value, Recurse); - } - else - { - Reflect.setProperty(basic, VariableName, Value); - } - } - } - } - - /** - * Go through and call the specified function on all members of the group, recursively by default. - * - * @param FunctionName The string representation of the function you want to call on each object, for example "kill()" or "init()". - * @param Args An array of arguments to call the function with - * @param Recurse Default value is true, meaning if callAll() encounters a member that is a group, it will call callAll() on that group rather than calling the group's function. - */ - public function callAll(FunctionName:String, ?Args:Array, Recurse:Bool = true):Void - { - if (Args == null) - Args = []; - - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null) - { - if (Recurse && (basic.collisionType == GROUP)) - { - (cast(basic, FlxTypedGroup)).callAll(FunctionName, Args, Recurse); - } - else - { - Reflect.callMethod(basic, Reflect.getProperty(basic, FunctionName), Args); - } - } - } - } - - /** - * Call this function to retrieve the first object with exists == false in the group. - * This is handy for recycling in general, e.g. respawning enemies. - * - * @param ObjectClass An optional parameter that lets you narrow the results to instances of this particular class. - * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. - * @return A FlxBasic currently flagged as not existing. - */ - public function getFirstAvailable(?ObjectClass:Class, Force:Bool = false):T - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; // we use basic as FlxBasic for performance reasons - - if ((basic != null) && !basic.exists && ((ObjectClass == null) || Std.is(basic, ObjectClass))) - { - if (Force && Type.getClassName(Type.getClass(basic)) != Type.getClassName(ObjectClass)) - { - continue; - } - return members[i - 1]; - } - } - - return null; - } - - /** - * Call this function to retrieve the first index set to 'null'. - * Returns -1 if no index stores a null object. - * - * @return An Int indicating the first null slot in the group. - */ - public function getFirstNull():Int - { - var i:Int = 0; - - while (i < length) - { - if (members[i] == null) - { - return i; - } - i++; - } - - return -1; - } - - /** - * Call this function to retrieve the first object with exists == true in the group. - * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. - * - * @return A FlxBasic currently flagged as existing. - */ - public function getFirstExisting():T - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && basic.exists) - { - return cast basic; - } - } - - return null; - } - - /** - * Call this function to retrieve the first object with dead == false in the group. - * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. - * - * @return A FlxBasic currently flagged as not dead. - */ - public function getFirstAlive():T - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; // we use basic as FlxBasic for performance reasons - - if (basic != null && basic.exists && basic.alive) - { - return cast basic; - } - } - - return null; - } - - /** - * Call this function to retrieve the first object with dead == true in the group. - * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. - * - * @return A FlxBasic currently flagged as dead. - */ - public function getFirstDead():T - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; // we use basic as FlxBasic for performance reasons - - if (basic != null && !basic.alive) - { - return cast basic; - } - } - - return null; - } - - /** - * Call this function to find out how many members of the group are not dead. - * - * @return The number of FlxBasics flagged as not dead. Returns -1 if group is empty. - */ - public function countLiving():Int - { - var i:Int = 0; - var count:Int = -1; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null) - { - if (count < 0) - { - count = 0; - } - if (basic.exists && basic.alive) - { - count++; - } - } - } - - return count; - } - - /** - * Call this function to find out how many members of the group are dead. - * - * @return The number of FlxBasics flagged as dead. Returns -1 if group is empty. - */ - public function countDead():Int - { - var i:Int = 0; - var count:Int = -1; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null) - { - if (count < 0) - { - count = 0; - } - if (!basic.alive) - { - count++; - } - } - } - - return count; - } - - /** - * Returns a member at random from the group. - * - * @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array. - * @param Length Optional restriction on the number of values you want to randomly select from. - * @return A FlxBasic from the members list. - */ - public function getRandom(StartIndex:Int = 0, Length:Int = 0):T - { - if (StartIndex < 0) - { - StartIndex = 0; - } - if (Length <= 0) - { - Length = length; - } - - return FlxArrayUtil.getRandom(members, StartIndex, Length); - } - - /** - * Remove all instances of FlxBasic subclass (FlxSprite, FlxBlock, etc) from the list. - * WARNING: does not destroy() or kill() any of these objects! - */ - public function clear():Void - { - length = 0; - FlxArrayUtil.clearArray(members); - } - - /** - * Calls kill on the group's members and then on the group itself. - * You can revive this group later via revive() after this. - */ - override public function kill():Void - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && basic.exists) - { - basic.kill(); - } - } - - super.kill(); - } - - /** - * Iterate through every member - * - * @return An iterator - */ - public inline function iterator(?filter:T->Bool):FlxTypedGroupIterator - { - return new FlxTypedGroupIterator(members, filter); - } - - /** - * Applies a function to all members - * - * @param Function A function that modifies one element at a time - */ - public function forEach(Function:T->Void) - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null) - Function(cast basic); - } - } - - /** - * Applies a function to all alive members - * - * @param Function A function that modifies one element at a time - */ - public function forEachAlive(Function:T->Void) - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && basic.exists && basic.alive) - Function(cast basic); - } - } - - /** - * Applies a function to all dead members - * - * @param Function A function that modifies one element at a time - */ - public function forEachDead(Function:T->Void) - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && !basic.alive) - Function(cast basic); - } - } - - /** - * Applies a function to all existing members - * - * @param Function A function that modifies one element at a time - */ - public function forEachExists(Function:T->Void) - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && basic.exists) - Function(cast basic); - } - } - - /** - * Applies a function to all members of type Class - * - * @param ObjectClass A class that objects will be checked against before Function is applied, ex: FlxSprite - * @param Function A function that modifies one element at a time - */ - public function forEachOfType(ObjectClass:Class, Function:K->Void) - { - var i:Int = 0; - var basic:FlxBasic = null; - - while (i < length) - { - basic = members[i++]; - - if (basic != null && Std.is(basic, ObjectClass)) - Function(cast basic); - } - } - - private function set_maxSize(Size:Int):Int - { - maxSize = Std.int(Math.abs(Size)); - - if (_marker >= maxSize) - { - _marker = 0; - } - if (maxSize == 0 || members == null || maxSize >= length) - { - return maxSize; - } - - // If the max size has shrunk, we need to get rid of some objects - var i:Int = maxSize; - var l:Int = length; - var basic:FlxBasic = null; - - while (i < l) - { - basic = members[i++]; - - if (basic != null) - basic.destroy(); - } - - FlxArrayUtil.setLength(members, maxSize); - length = members.length; - - return maxSize; - } -} - -/** - * Iterator implementation for groups - * Support a filter method (used for iteratorAlive, iteratorDead and iteratorExists) - * @author Masadow - */ -class FlxTypedGroupIterator -{ - private var _groupMembers:Array; - private var _filter:T->Bool; - private var _cursor:Int; - private var _length:Int; - - public function new(GroupMembers:Array, ?filter:T->Bool) - { - _groupMembers = GroupMembers; - _filter = filter; - _cursor = 0; - _length = _groupMembers.length; - } - - public function next() - { - return hasNext() ? _groupMembers[_cursor++] : null; - } - - public function hasNext():Bool - { - while (_cursor < _length && (_groupMembers[_cursor] == null || _filter != null && !_filter(_groupMembers[_cursor]))) - { - _cursor++; - } - return _cursor < _length; - } -} diff --git a/flixel/group/FlxTypedSpriteGroup.hx b/flixel/group/FlxTypedSpriteGroup.hx deleted file mode 100644 index 303c673928..0000000000 --- a/flixel/group/FlxTypedSpriteGroup.hx +++ /dev/null @@ -1,1024 +0,0 @@ -package flixel.group; - -import flash.display.BitmapData; -import flash.display.BlendMode; -import flash.geom.ColorTransform; -import flixel.FlxCamera; -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.group.FlxTypedGroup; -import flixel.system.FlxAssets.FlxGraphicAsset; -import flixel.system.FlxAssets.FlxTextureAsset; -import flixel.system.layer.frames.FlxFrame; -import flixel.util.FlxDestroyUtil; -import flixel.math.FlxPoint; -import flixel.util.FlxSort; - -/** - * FlxSpriteGroup is a special FlxSprite that can be treated like - * a single sprite even if it's made up of several member sprites. - * It shares the FlxTypedGroup API, but it doesn't inherit from it. - */ -class FlxTypedSpriteGroup extends FlxSprite -{ - /** - * The actual group which holds all sprites - */ - public var group:FlxTypedGroup; - - /** - * The link to a group's members array - */ - public var members(get, null):Array; - - /** - * The number of entries in the members array. For performance and safety you should check this - * variable instead of members.length unless you really know what you're doing! - */ - public var length(get, null):Int; - - /** - * The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow. - */ - public var maxSize(get, set):Int; - - /** - * Optimization to allow setting position of group without transforming children twice. - */ - private var _skipTransformChildren:Bool = false; - - #if !FLX_NO_DEBUG - /** - * Just a helper variable to check if this group has already been drawn on debug layer - */ - private var _isDrawnDebug:Bool = false; - #end - - /** - * Array of all the FlxSprites that exist in this group for - * optimization purposes / static typing on cpp targets. - */ - private var _sprites:Array; - - /** - * @param X The initial X position of the group - * @param Y The initial Y position of the group - * @param MaxSize Maximum amount of members allowed - */ - public function new(X:Float = 0, Y:Float = 0, MaxSize:Int = 0) - { - super(X, Y); - group = new FlxTypedGroup(MaxSize); - _sprites = cast group.members; - } - - /** - * This method is used for initialization of variables of complex types. - * Don't forget to call super.initVars() if you'll override this method, - * or you'll get null object error and app will crash - */ - override private function initVars():Void - { - collisionType = SPRITEGROUP; - - offset = new FlxCallbackPoint(offsetCallback); - origin = new FlxCallbackPoint(originCallback); - scale = new FlxCallbackPoint(scaleCallback); - scrollFactor = new FlxCallbackPoint(scrollFactorCallback); - - scale.set(1, 1); - scrollFactor.set(1, 1); - - initMotionVars(); - } - - /** - * WARNING: This will remove this object entirely. Use kill() if you want to disable it temporarily only and reset() it later to revive it. - * Override this function to null out variables manually or call destroy() on class members if necessary. Don't forget to call super.destroy()! - */ - override public function destroy():Void - { - // normally don't have to destroy FlxPoints, but these are FlxCallbackPoints! - offset = FlxDestroyUtil.destroy(offset); - origin = FlxDestroyUtil.destroy(origin); - scale = FlxDestroyUtil.destroy(scale); - scrollFactor = FlxDestroyUtil.destroy(scrollFactor); - - group = FlxDestroyUtil.destroy(group); - _sprites = null; - - super.destroy(); - } - - /** - * Recursive cloning method: it will create copy of this group which will hold copies of all sprites - * - * @param NewSprite optional sprite group to copy to - * @return copy of this sprite group - */ - override public function clone(?NewSprite:FlxSprite):FlxTypedSpriteGroup - { - if (NewSprite == null || !Std.is(NewSprite, FlxTypedSpriteGroup)) - { - NewSprite = new FlxTypedSpriteGroup(0, 0, group.maxSize); - } - - var cloned:FlxTypedSpriteGroup = cast NewSprite; - cloned.maxSize = group.maxSize; - - for (sprite in _sprites) - { - if (sprite != null) - { - cloned.add(cast sprite.clone()); - } - } - return cloned; - } - - /** - * Check and see if any sprite in this group is currently on screen. - * - * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera. - * @return Whether the object is on screen or not. - */ - override public function isOnScreen(?Camera:FlxCamera):Bool - { - var result:Bool = false; - for (sprite in _sprites) - { - if (sprite != null && sprite.exists && sprite.visible) - { - result = result || sprite.isOnScreen(Camera); - } - } - - return result; - } - - /** - * Checks to see if a point in 2D world space overlaps any FlxSprite object from this group. - * - * @param Point The point in world space you want to check. - * @param InScreenSpace Whether to take scroll factors into account when checking for overlap. - * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera. - * @return Whether or not the point overlaps this group. - */ - override public function overlapsPoint(point:FlxPoint, InScreenSpace:Bool = false, ?Camera:FlxCamera):Bool - { - var result:Bool = false; - for (sprite in _sprites) - { - if ((sprite != null) && sprite.exists && sprite.visible) - { - result = result || sprite.overlapsPoint(point, InScreenSpace, Camera); - } - } - - return result; - } - - /** - * Checks to see if a point in 2D world space overlaps any of FlxSprite object's current displayed pixels. - * This check is ALWAYS made in screen space, and always takes scroll factors into account. - * - * @param Point The point in world space you want to check. - * @param Mask Used in the pixel hit test to determine what counts as solid. - * @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera. - * @return Whether or not the point overlaps this object. - */ - override public function pixelsOverlapPoint(point:FlxPoint, Mask:Int = 0xFF, ?Camera:FlxCamera):Bool - { - var result:Bool = false; - for (sprite in _sprites) - { - if ((sprite != null) && sprite.exists && sprite.visible) - { - result = result || sprite.pixelsOverlapPoint(point, Mask, Camera); - } - } - - return result; - } - - override public function update():Void - { - group.update(); - - if (moves) - { - updateMotion(); - } - } - - override public function draw():Void - { - group.draw(); - #if !FLX_NO_DEBUG - _isDrawnDebug = false; - #end - } - - /** - * Replaces all pixels with specified Color with NewColor pixels. This operation is applied to every nested sprite from this group - * - * @param Color Color to replace - * @param NewColor New color - * @param FetchPositions Whether we need to store positions of pixels which colors were replaced - * @return Array replaced pixels positions - */ - override public function replaceColor(Color:Int, NewColor:Int, FetchPositions:Bool = false):Array - { - var positions:Array = null; - if (FetchPositions) - { - positions = new Array(); - } - - var spritePositions:Array; - for (sprite in _sprites) - { - if (sprite != null) - { - spritePositions = sprite.replaceColor(Color, NewColor, FetchPositions); - if (FetchPositions) - { - positions = positions.concat(spritePositions); - } - } - } - - return positions; - } - - /** - * Adds a new FlxSprite subclass to the group. - * - * @param Object The sprite or sprite group you want to add to the group. - * @return The same object that was passed in. - */ - public function add(Sprite:T):T - { - var sprite:FlxSprite = cast Sprite; - sprite.x += x; - sprite.y += y; - sprite.alpha *= alpha; - sprite.scrollFactor.copyFrom(scrollFactor); - sprite.cameras = _cameras; // _cameras instead of cameras because get_cameras() will not return null - return group.add(Sprite); - } - - /** - * Recycling is designed to help you reuse game objects without always re-allocating or "newing" them. - * - * @param ObjectClass The class type you want to recycle (e.g. FlxSprite, EvilRobot, etc). Do NOT "new" the class in the parameter! - * @param ContructorArgs An array of arguments passed into a newly object if there aren't any dead members to recycle. - * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. - * @return A reference to the object that was created. Don't forget to cast it back to the Class you want (e.g. myObject = myGroup.recycle(myObjectClass) as myObjectClass;). - */ - public inline function recycle(?ObjectClass:Class, ?ContructorArgs:Array, Force:Bool = false):FlxSprite - { - return group.recycle(ObjectClass, ContructorArgs, Force); - } - - /** - * Removes specified sprite from the group. - * - * @param Object The FlxSprite you want to remove. - * @param Splice Whether the object should be cut from the array entirely or not. - * @return The removed object. - */ - public function remove(Object:T, Splice:Bool = false):T - { - return group.remove(Object, Splice); - } - - /** - * Replaces an existing FlxSprite with a new one. - * - * @param OldObject The object you want to replace. - * @param NewObject The new object you want to use instead. - * @return The new object. - */ - public inline function replace(OldObject:T, NewObject:T):T - { - return group.replace(OldObject, NewObject); - } - - /** - * Call this function to sort the group according to a particular value and order. For example, to sort game objects for Zelda-style - * overlaps you might call myGroup.sort(FlxSort.byY, FlxSort.ASCENDING) at the bottom of your FlxState.update() override. - * - * @param Function The sorting function to use - you can use one of the premade ones in FlxSort or write your own using FlxSort.byValues() as a backend - * @param Order A FlxGroup constant that defines the sort order. Possible values are FlxSort.ASCENDING (default) and FlxSort.DESCENDING. - */ - public inline function sort(Function:Int->T->T->Int, Order:Int = FlxSort.ASCENDING):Void - { - group.sort(Function, Order); - } - - /** - * Go through and set the specified variable to the specified value on all members of the group. - * - * @param VariableName The string representation of the variable name you want to modify, for example "visible" or "scrollFactor". - * @param Value The value you want to assign to that variable. - * @param Recurse Default value is true, meaning if setAll() encounters a member that is a group, it will call setAll() on that group rather than modifying its variable. - */ - public inline function setAll(VariableName:String, Value:Dynamic, Recurse:Bool = true):Void - { - group.setAll(VariableName, Value, Recurse); - } - - /** - * Go through and call the specified function on all members of the group. - * Currently only works on functions that have no required parameters. - * - * @param FunctionName The string representation of the function you want to call on each object, for example "kill()" or "init()". - * @param Recurse Default value is true, meaning if callAll() encounters a member that is a group, it will call callAll() on that group rather than calling the group's function. - */ - public inline function callAll(FunctionName:String, ?Args:Array, Recurse:Bool = true):Void - { - group.callAll(FunctionName, Args, Recurse); - } - - /** - * Call this function to retrieve the first object with exists == false in the group. - * This is handy for recycling in general, e.g. respawning enemies. - * - * @param ObjectClass An optional parameter that lets you narrow the results to instances of this particular class. - * @param Force Force the object to be an ObjectClass and not a super class of ObjectClass. - * @return A FlxSprite currently flagged as not existing. - */ - public inline function getFirstAvailable(?ObjectClass:Class, Force:Bool = false):T - { - return group.getFirstAvailable(ObjectClass, Force); - } - - /** - * Call this function to retrieve the first index set to 'null'. - * Returns -1 if no index stores a null object. - * - * @return An Int indicating the first null slot in the group. - */ - public inline function getFirstNull():Int - { - return group.getFirstNull(); - } - - /** - * Call this function to retrieve the first object with exists == true in the group. - * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. - * - * @return A FlxSprite currently flagged as existing. - */ - public inline function getFirstExisting():T - { - return group.getFirstExisting(); - } - - /** - * Call this function to retrieve the first object with dead == false in the group. - * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. - * - * @return A FlxSprite currently flagged as not dead. - */ - public inline function getFirstAlive():T - { - return group.getFirstAlive(); - } - - /** - * Call this function to retrieve the first object with dead == true in the group. - * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. - * - * @return A FlxSprite currently flagged as dead. - */ - public inline function getFirstDead():T - { - return group.getFirstDead(); - } - - /** - * Call this function to find out how many members of the group are not dead. - * - * @return The number of FlxSprites flagged as not dead. Returns -1 if group is empty. - */ - public inline function countLiving():Int - { - return group.countLiving(); - } - - /** - * Call this function to find out how many members of the group are dead. - * - * @return The number of FlxSprites flagged as dead. Returns -1 if group is empty. - */ - public inline function countDead():Int - { - return group.countDead(); - } - - /** - * Returns a member at random from the group. - * - * @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array. - * @param Length Optional restriction on the number of values you want to randomly select from. - * @return A FlxSprite from the members list. - */ - public inline function getRandom(StartIndex:Int = 0, Length:Int = 0):T - { - return group.getRandom(StartIndex, Length); - } - - /** - * Iterate through every member - * - * @return An iterator - */ - public inline function iterator(?filter:T->Bool):FlxTypedGroupIterator - { - return new FlxTypedGroupIterator(members, filter); - } - - /** - * Applies a function to all members - * - * @param Function A function that modifies one element at a time - */ - public inline function forEach(Function:T->Void):Void - { - group.forEach(Function); - } - - /** - * Applies a function to all alive members - * - * @param Function A function that modifies one element at a time - */ - public inline function forEachAlive(Function:T->Void):Void - { - group.forEachAlive(Function); - } - - /** - * Applies a function to all dead members - * - * @param Function A function that modifies one element at a time - */ - public inline function forEachDead(Function:T->Void):Void - { - group.forEachDead(Function); - } - - /** - * Applies a function to all existing members - * - * @param Function A function that modifies one element at a time - */ - public inline function forEachExists(Function:T->Void):Void - { - group.forEachExists(Function); - } - - /** - * Applies a function to all members of type Class - * - * @param ObjectClass A class that objects will be checked against before Function is applied, ex: FlxSprite - * @param Function A function that modifies one element at a time - */ - public inline function forEachOfType(ObjectClass:Class, Function:K->Void) - { - group.forEachOfType(ObjectClass, Function); - } - - /** - * Remove all instances of FlxSprite from the list. - * WARNING: does not destroy() or kill() any of these objects! - */ - public inline function clear():Void - { - group.clear(); - } - - /** - * Calls kill on the group's members and then on the group itself. - * You can revive this group later via revive() after this. - */ - override public function kill():Void - { - super.kill(); - group.kill(); - } - - /** - * Revives the group. - */ - override public function revive():Void - { - super.revive(); - group.revive(); - } - - /** - * Helper function for the sort process. - * - * @param Obj1 The first object being sorted. - * @param Obj2 The second object being sorted. - * @return An integer value: -1 (Obj1 before Obj2), 0 (same), or 1 (Obj1 after Obj2). - */ - override public function reset(X:Float, Y:Float):Void - { - revive(); - setPosition(X, Y); - - for (sprite in _sprites) - { - if (sprite != null) - { - sprite.reset(X, Y); - } - } - } - - /** - * Helper function to set the coordinates of this object. - * Handy since it only requires one line of code. - * - * @param X The new x position - * @param Y The new y position - */ - override public function setPosition(X:Float = 0, Y:Float = 0):Void - { - // Transform children by the movement delta - var dx:Float = X - x; - var dy:Float = Y - y; - multiTransformChildren([xTransform, yTransform], [dx, dy]); - - // don't transform children twice - _skipTransformChildren = true; - x = X; // this calls set_x - y = Y; // this calls set_y - _skipTransformChildren = false; - } - - /** - * Handy function that allows you to quickly transform one property of sprites in this group at a time. - * - * @param Function Function to transform the sprites. Example: function(s:FlxSprite, v:Dynamic) { s.acceleration.x = v; s.makeGraphic(10,10,0xFF000000); } - * @param Value Value which will passed to lambda function - */ - @:generic - public function transformChildren(Function:T->V->Void, Value:V):Void - { - if (group == null) - { - return; - } - - for (sprite in _sprites) - { - if ((sprite != null) && sprite.exists) - { - Function(cast sprite, Value); - } - } - } - - /** - * Handy function that allows you to quickly transform multiple properties of sprites in this group at a time. - * - * @param FunctionArray Array of functions to transform sprites in this group. - * @param ValueArray Array of values which will be passed to lambda functions - */ - @:generic - public function multiTransformChildren(FunctionArray:ArrayV->Void>, ValueArray:Array):Void - { - if (group == null) - { - return; - } - - var numProps:Int = FunctionArray.length; - if (numProps > ValueArray.length) - { - return; - } - - var lambda:T->V->Void; - for (sprite in _sprites) - { - if ((sprite != null) && sprite.exists) - { - for (i in 0...numProps) - { - lambda = FunctionArray[i]; - lambda(cast sprite, ValueArray[i]); - } - } - } - } - - // PROPERTIES GETTERS/SETTERS - - override private function set_cameras(Value:Array):Array - { - if (cameras != Value) - transformChildren(camerasTransform, Value); - return super.set_cameras(Value); - } - - override private function set_exists(Value:Bool):Bool - { - if (exists != Value) - transformChildren(existsTransform, Value); - return super.set_exists(Value); - } - - override private function set_visible(Value:Bool):Bool - { - if (exists && visible != Value) - transformChildren(visibleTransform, Value); - return super.set_visible(Value); - } - - override private function set_active(Value:Bool):Bool - { - if (exists && active != Value) - transformChildren(activeTransform, Value); - return super.set_active(Value); - } - - override private function set_alive(Value:Bool):Bool - { - if (exists && alive != Value) - transformChildren(aliveTransform, Value); - return super.set_alive(Value); - } - - override private function set_x(Value:Float):Float - { - if (!_skipTransformChildren && exists && x != Value) - { - var offset:Float = Value - x; - transformChildren(xTransform, offset); - } - - return x = Value; - } - - override private function set_y(Value:Float):Float - { - if (!_skipTransformChildren && exists && y != Value) - { - var offset:Float = Value - y; - transformChildren(yTransform, offset); - } - - return y = Value; - } - - override private function set_angle(Value:Float):Float - { - if (exists && angle != Value) - { - var offset:Float = Value - angle; - transformChildren(angleTransform, offset); - } - return angle = Value; - } - - override private function set_alpha(Value:Float):Float - { - if (Value > 1) - { - Value = 1; - } - else if (Value < 0) - { - Value = 0; - } - - if (exists && alpha != Value) - { - var factor:Float = (alpha > 0) ? Value / alpha : 0; - transformChildren(alphaTransform, factor); - } - return alpha = Value; - } - - override private function set_facing(Value:Int):Int - { - if (exists && facing != Value) - transformChildren(facingTransform, Value); - return facing = Value; - } - - override private function set_flipX(Value:Bool):Bool - { - if (exists && flipX != Value) - transformChildren(flipXTransform, Value); - return flipX = Value; - } - - override private function set_flipY(Value:Bool):Bool - { - if (exists && flipY != Value) - transformChildren(flipYTransform, Value); - return flipY = Value; - } - - override private function set_moves(Value:Bool):Bool - { - if (exists && moves != Value) - transformChildren(movesTransform, Value); - return moves = Value; - } - - override private function set_immovable(Value:Bool):Bool - { - if (exists && immovable != Value) - transformChildren(immovableTransform, Value); - return immovable = Value; - } - - override private function set_solid(Value:Bool):Bool - { - if (exists && solid != Value) - transformChildren(solidTransform, Value); - return super.set_solid(Value); - } - - override private function set_color(Value:Int):Int - { - if (exists && color != Value) - transformChildren(gColorTransform, Value); - return color = Value; - } - - override private function set_blend(Value:BlendMode):BlendMode - { - if (exists && blend != Value) - transformChildren(blendTransform, Value); - return blend = Value; - } - - override private function set_pixelPerfectRender(Value:Bool):Bool - { - if (exists && pixelPerfectRender != Value) - transformChildren(pixelPerfectTransform, Value); - return super.set_pixelPerfectRender(Value); - } - - /** - * This functionality isn't supported in SpriteGroup - */ - override private function set_width(Value:Float):Float - { - return Value; - } - - override private function get_width():Float - { - if (length == 0) - { - return 0; - } - - var minX:Float = Math.POSITIVE_INFINITY; - var maxX:Float = Math.NEGATIVE_INFINITY; - - for (member in _sprites) - { - var minMemberX:Float = member.x; - var maxMemberX:Float = minMemberX + member.width; - - if (maxMemberX > maxX) - { - maxX = maxMemberX; - } - if (minMemberX < minX) - { - minX = minMemberX; - } - } - return (maxX - minX); - } - - /** - * This functionality isn't supported in SpriteGroup - */ - override private function set_height(Value:Float):Float - { - return Value; - } - - override private function get_height():Float - { - if (length == 0) - { - return 0; - } - - var minY:Float = Math.POSITIVE_INFINITY; - var maxY:Float = Math.NEGATIVE_INFINITY; - - for (member in _sprites) - { - var minMemberY:Float = member.y; - var maxMemberY:Float = minMemberY + member.height; - - if (maxMemberY > maxY) - { - maxY = maxMemberY; - } - if (minMemberY < minY) - { - minY = minMemberY; - } - } - return (maxY - minY); - } - - // GROUP FUNCTIONS - - private inline function get_length():Int - { - return group.length; - } - - private inline function get_maxSize():Int - { - return group.maxSize; - } - - private inline function set_maxSize(Size:Int):Int - { - return group.maxSize = Size; - } - - private inline function get_members():Array - { - return group.members; - } - - // TRANSFORM FUNCTIONS - STATIC TYPING - - private inline function xTransform(Sprite:FlxSprite, X:Float) { Sprite.x += X; } // addition - private inline function yTransform(Sprite:FlxSprite, Y:Float) { Sprite.y += Y; } // addition - private inline function angleTransform(Sprite:FlxSprite, Angle:Float) { Sprite.angle += Angle; } // addition - private inline function alphaTransform(Sprite:FlxSprite, Alpha:Float) { Sprite.alpha *= Alpha; } // multiplication - private inline function facingTransform(Sprite:FlxSprite, Facing:Int) { Sprite.facing = Facing; } // set - private inline function flipXTransform(Sprite:FlxSprite, FlipX:Bool) { Sprite.flipX = FlipX; } // set - private inline function flipYTransform(Sprite:FlxSprite, FlipY:Bool) { Sprite.flipY = FlipY; } // set - private inline function movesTransform(Sprite:FlxSprite, Moves:Bool) { Sprite.moves = Moves; } // set - private inline function pixelPerfectTransform(Sprite:FlxSprite, PixelPerfect:Bool) { Sprite.pixelPerfectRender = PixelPerfect; } // set - private inline function gColorTransform(Sprite:FlxSprite, Color:Int) { Sprite.color = Color; } // set - private inline function blendTransform(Sprite:FlxSprite, Blend:BlendMode) { Sprite.blend = Blend; } // set - private inline function immovableTransform(Sprite:FlxSprite, Immovable:Bool) { Sprite.immovable = Immovable; } // set - private inline function visibleTransform(Sprite:FlxSprite, Visible:Bool) { Sprite.visible = Visible; } // set - private inline function activeTransform(Sprite:FlxSprite, Active:Bool) { Sprite.active = Active; } // set - private inline function solidTransform(Sprite:FlxSprite, Solid:Bool) { Sprite.solid = Solid; } // set - private inline function aliveTransform(Sprite:FlxSprite, Alive:Bool) { Sprite.alive = Alive; } // set - private inline function existsTransform(Sprite:FlxSprite, Exists:Bool) { Sprite.exists = Exists; } // set - private inline function camerasTransform(Sprite:FlxSprite, Cameras:Array) { Sprite.cameras = Cameras; } // set - - private inline function offsetTransform(Sprite:FlxSprite, Offset:FlxPoint) { Sprite.offset.copyFrom(Offset); } // set - private inline function originTransform(Sprite:FlxSprite, Origin:FlxPoint) { Sprite.origin.copyFrom(Origin); } // set - private inline function scaleTransform(Sprite:FlxSprite, Scale:FlxPoint) { Sprite.scale.copyFrom(Scale); } // set - private inline function scrollFactorTransform(Sprite:FlxSprite, ScrollFactor:FlxPoint) { Sprite.scrollFactor.copyFrom(ScrollFactor); } // set - - // Functions for the FlxCallbackPoint - private inline function offsetCallback(Offset:FlxPoint) { transformChildren(offsetTransform, Offset); } - private inline function originCallback(Origin:FlxPoint) { transformChildren(originTransform, Origin); } - private inline function scaleCallback(Scale:FlxPoint) { transformChildren(scaleTransform, Scale); } - private inline function scrollFactorCallback(ScrollFactor:FlxPoint) { transformChildren(scrollFactorTransform, ScrollFactor); } - - // NON-SUPPORTED FUNCTIONALITY - // THESE METHODS ARE OVERRIDEN FOR SAFETY PURPOSES - - /** - * This functionality isn't supported in SpriteGroup - * @return this sprite group - */ - override public function loadGraphicFromSprite(Sprite:FlxSprite):FlxSprite - { - #if !FLX_NO_DEBUG - FlxG.log.error("loadGraphicFromSprite() is not supported in FlxSpriteGroups."); - #end - return this; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return this sprite group - */ - override public function loadGraphic(Graphic:FlxGraphicAsset, Animated:Bool = false, Width:Int = 0, Height:Int = 0, Unique:Bool = false, ?Key:String):FlxSprite - { - return this; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return this sprite group - */ - override public function loadRotatedGraphic(Graphic:FlxGraphicAsset, Rotations:Int = 16, Frame:Int = -1, AntiAliasing:Bool = false, AutoBuffer:Bool = false, ?Key:String):FlxSprite - { - #if !FLX_NO_DEBUG - FlxG.log.error("loadRotatedGraphic() is not supported in FlxSpriteGroups."); - #end - return this; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return this sprite group - */ - override public function makeGraphic(Width:Int, Height:Int, Color:Int = 0xffffffff, Unique:Bool = false, ?Key:String):FlxSprite - { - #if !FLX_NO_DEBUG - FlxG.log.error("makeGraphic() is not supported in FlxSpriteGroups."); - #end - return this; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return this sprite group - */ - override public function loadGraphicFromTexture(Data:FlxTextureAsset, Unique:Bool = false, ?FrameName:String):FlxSprite - { - #if !FLX_NO_DEBUG - FlxG.log.error("loadGraphicFromTexture() is not supported in FlxSpriteGroups."); - #end - return this; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return this sprite group - */ - override public function loadRotatedGraphicFromTexture(Data:Dynamic, Image:String, Rotations:Int = 16, AntiAliasing:Bool = false, AutoBuffer:Bool = false):FlxSprite - { - #if !FLX_NO_DEBUG - FlxG.log.error("loadRotatedGraphicFromTexture() is not supported in FlxSpriteGroups."); - #end - return this; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return the BitmapData passed in as parameter - */ - override private function set_pixels(Value:BitmapData):BitmapData - { - return Value; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return the FlxFrame passed in as parameter - */ - override private function set_frame(Value:FlxFrame):FlxFrame - { - return Value; - } - - /** - * This functionality isn't supported in SpriteGroup - * @return WARNING: returns null - */ - override private function get_pixels():BitmapData - { - return null; - } - - /** - * Internal function to update the current animation frame. - * - * @param RunOnCpp Whether the frame should also be recalculated if we're on a non-flash target - */ - override private inline function calcFrame(RunOnCpp:Bool = false):Void - { - // Nothing to do here - } - - /** - * This functionality isn't supported in SpriteGroup - */ - override private inline function resetHelpers():Void {} - - /** - * This functionality isn't supported in SpriteGroup - */ - override public inline function stamp(Brush:FlxSprite, X:Int = 0, Y:Int = 0):Void {} - - /** - * This functionality isn't supported in SpriteGroup - */ - override private inline function updateColorTransform():Void {} - - /** - * This functionality isn't supported in SpriteGroup - */ - override public inline function updateFrameData():Void {} -} \ No newline at end of file diff --git a/flixel/input/mouse/FlxMouseEventManager.hx b/flixel/input/mouse/FlxMouseEventManager.hx index 78f778d61e..e97627d9cc 100644 --- a/flixel/input/mouse/FlxMouseEventManager.hx +++ b/flixel/input/mouse/FlxMouseEventManager.hx @@ -7,7 +7,6 @@ import flixel.FlxG; import flixel.FlxObject; import flixel.FlxSprite; import flixel.group.FlxGroup; -import flixel.group.FlxTypedGroup; import flixel.math.FlxAngle; import flixel.util.FlxDestroyUtil; import flixel.math.FlxPoint; diff --git a/flixel/input/touch/FlxTouch.hx b/flixel/input/touch/FlxTouch.hx index f17c9349dd..54e9867c43 100644 --- a/flixel/input/touch/FlxTouch.hx +++ b/flixel/input/touch/FlxTouch.hx @@ -5,10 +5,10 @@ import flixel.FlxBasic; import flixel.FlxCamera; import flixel.FlxG; import flixel.FlxObject; -import flixel.group.FlxTypedGroup; +import flixel.group.FlxGroup.FlxTypedGroup; import flixel.input.FlxSwipe; -import flixel.util.FlxDestroyUtil; import flixel.math.FlxPoint; +import flixel.util.FlxDestroyUtil; /** * Helper class, contains and track touch points in your game. diff --git a/flixel/system/FlxQuadTree.hx b/flixel/system/FlxQuadTree.hx index 26a69c39a0..120269d884 100644 --- a/flixel/system/FlxQuadTree.hx +++ b/flixel/system/FlxQuadTree.hx @@ -4,7 +4,6 @@ import flixel.FlxBasic; import flixel.FlxObject; import flixel.group.FlxGroup; import flixel.group.FlxSpriteGroup; -import flixel.group.FlxTypedGroup; import flixel.util.FlxDestroyUtil; import flixel.math.FlxRect; diff --git a/flixel/system/debug/Tracker.hx b/flixel/system/debug/Tracker.hx index 0a82b58ac0..9af5a00f41 100644 --- a/flixel/system/debug/Tracker.hx +++ b/flixel/system/debug/Tracker.hx @@ -13,7 +13,7 @@ import flixel.FlxSprite; import flixel.FlxState; import flixel.FlxSubState; import flixel.group.FlxSpriteGroup; -import flixel.group.FlxTypedGroup.FlxTypedGroup; +import flixel.group.FlxGroup; import flixel.input.gamepad.FlxGamepad; import flixel.input.mouse.FlxMouse; import flixel.input.touch.FlxTouch; @@ -27,7 +27,7 @@ import flixel.tweens.FlxTween; #if !bitfive import flixel.ui.FlxBar; #end -import flixel.ui.FlxTypedButton.FlxTypedButton; +import flixel.ui.FlxButton; import flixel.util.FlxPath; import flixel.math.FlxPoint; import flixel.math.FlxRect; @@ -35,7 +35,7 @@ import flixel.util.FlxTimer; #end import flixel.animation.FlxAnimationController; -import flixel.effects.particles.FlxTypedEmitter; +import flixel.effects.particles.FlxEmitter; import flixel.util.FlxStringUtil; class Tracker extends Watch diff --git a/flixel/tile/FlxTilemap.hx b/flixel/tile/FlxTilemap.hx index a6d921a899..7f13c6bf73 100644 --- a/flixel/tile/FlxTilemap.hx +++ b/flixel/tile/FlxTilemap.hx @@ -9,7 +9,6 @@ import flixel.FlxCamera; import flixel.FlxG; import flixel.FlxObject; import flixel.group.FlxGroup; -import flixel.group.FlxTypedGroup; import flixel.system.FlxAssets; import flixel.system.layer.DrawStackItem; import flixel.system.layer.frames.FlxSpriteFrames; diff --git a/flixel/ui/FlxButton.hx b/flixel/ui/FlxButton.hx index 3ca30e7efa..da4bd9af68 100644 --- a/flixel/ui/FlxButton.hx +++ b/flixel/ui/FlxButton.hx @@ -1,7 +1,17 @@ package flixel.ui; -import flixel.text.FlxText; +import flash.display.BitmapData; +import flash.events.MouseEvent; +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.input.touch.FlxTouch; import flixel.math.FlxPoint; +import flixel.system.FlxSound; +import flixel.text.FlxText; +import flixel.util.FlxDestroyUtil; + +@:bitmap("assets/images/ui/button.png") +private class GraphicButton extends BitmapData {} /** * A simple button class that calls a function when clicked by the mouse. @@ -77,4 +87,429 @@ class FlxButton extends FlxTypedButton { return label.text = Text; } -} \ No newline at end of file +} + +/** + * A simple button class that calls a function when clicked by the mouse. + */ +class FlxTypedButton extends FlxSprite +{ + /** + * The label that appears on the button. Can be any FlxSprite. + */ + public var label(default, set):T; + /** + * What offsets the label should have for each status. + */ + public var labelOffsets:Array; + /** + * What alpha value the label should have for each status. Default is [0.8, 1.0, 0.5]. + */ + public var labelAlphas:Array; + /** + * Whether you can press the button simply by releasing the touch / mouse button over it (default). + * If false, the input has to be pressed while hovering over the button. + */ + public var allowSwiping:Bool = true; + /** + * Whether to allow the HIHGLIGHT frame of the button graphic to be used on mobile + * (false by default, the NORMAL graphic is used instead then). + */ + public var allowHighlightOnMobile:Bool = false; + /** + * Shows the current state of the button, either FlxButton.NORMAL, + * FlxButton.HIGHLIGHT or FlxButton.PRESSED. + */ + public var status(default, set):Int; + /** + * The properties of this button's onUp event (callback function, sound). + */ + public var onUp(default, null):FlxButtonEvent; + /** + * The properties of this button's onDown event (callback function, sound). + */ + public var onDown(default, null):FlxButtonEvent; + /** + * The properties of this button's onOver event (callback function, sound). + */ + public var onOver(default, null):FlxButtonEvent; + /** + * The properties of this button's onOut event (callback function, sound). + */ + public var onOut(default, null):FlxButtonEvent; + + /** + * The touch currently pressing this button, if none, it's null. Needed to check for its release. + */ + private var _pressedTouch:FlxTouch; + /** + * Whether this button is currently being pressed by the mouse. Needed to check for its release. + */ + private var _pressedMouse:Bool = false; + + /** + * Creates a new FlxTypedButton object with a gray background. + * + * @param X The X position of the button. + * @param Y The Y position of the button. + * @param OnClick The function to call whenever the button is clicked. + */ + public function new(X:Float = 0, Y:Float = 0, ?OnClick:Void->Void) + { + super(X, Y); + + loadGraphic(GraphicButton, true, 80, 20); + + onUp = new FlxButtonEvent(OnClick); + onDown = new FlxButtonEvent(); + onOver = new FlxButtonEvent(); + onOut = new FlxButtonEvent(); + + labelAlphas = [0.8, 1.0, 0.5]; + labelOffsets = [FlxPoint.get(), FlxPoint.get(), FlxPoint.get(0, 1)]; + + status = FlxButton.NORMAL; + + // Since this is a UI element, the default scrollFactor is (0, 0) + scrollFactor.set(); + + #if !FLX_NO_MOUSE + FlxG.stage.addEventListener(MouseEvent.MOUSE_UP, onUpEventListener); + #end + } + + /** + * Called by the game state when state is changed (if this object belongs to the state) + */ + override public function destroy():Void + { + label = FlxDestroyUtil.destroy(label); + + onUp = FlxDestroyUtil.destroy(onUp); + onDown = FlxDestroyUtil.destroy(onDown); + onOver = FlxDestroyUtil.destroy(onOver); + onOut = FlxDestroyUtil.destroy(onOut); + + labelOffsets = FlxDestroyUtil.putArray(labelOffsets); + + labelAlphas = null; + _pressedTouch = null; + + #if !FLX_NO_MOUSE + FlxG.stage.removeEventListener(MouseEvent.MOUSE_UP, onUpEventListener); + #end + + super.destroy(); + } + + /** + * Called by the game loop automatically, handles mouseover and click detection. + */ + override public function update():Void + { + super.update(); + + if (!visible) + { + return; + } + + // Update the button, but only if at least either mouse or touches are enabled + #if (!FLX_NO_MOUSE || !FLX_NO_TOUCH) + updateButton(); + #end + + // Pick the appropriate animation frame + var nextFrame:Int = status; + + // "Highlight" doesn't make much sense on mobile devices / touchscreens + #if mobile + if (!allowHighlightOnMobile && (nextFrame == FlxButton.HIGHLIGHT)) + { + nextFrame = FlxButton.NORMAL; + } + #end + + animation.frameIndex = nextFrame; + } + + /** + * Just draws the button graphic and text label to the screen. + */ + override public function draw():Void + { + super.draw(); + + if (label != null && label.visible) + { + label.cameras = cameras; + label.draw(); + } + } + + #if !FLX_NO_DEBUG + /** + * Helper function to draw the debug graphic for the label as well. + */ + override public function drawDebug():Void + { + super.drawDebug(); + + if (label != null) + { + label.drawDebug(); + } + } + #end + + /** + * Basic button update logic - searches for overlaps with touches and + * the mouse cursor and calls updateStatus() + */ + private function updateButton():Void + { + // We're looking for any touch / mouse overlaps with this button + var overlapFound = false; + + // Have a look at all cameras + for (camera in cameras) + { + #if !FLX_NO_MOUSE + FlxG.mouse.getWorldPosition(camera, _point); + + if (overlapsPoint(_point, true, camera)) + { + overlapFound = true; + updateStatus(true, FlxG.mouse.justPressed, FlxG.mouse.pressed); + break; + } + #end + + #if !FLX_NO_TOUCH + for (touch in FlxG.touches.list) + { + touch.getWorldPosition(camera, _point); + + if (overlapsPoint(_point, true, camera)) + { + overlapFound = true; + updateStatus(true, touch.justPressed, touch.pressed, touch); + break; + } + } + #end + } + + if (!overlapFound) + { + updateStatus(false, false, false); + } + } + + /** + * Updates the button status by calling the respective event handler function. + * + * @param Overlap Whether there was any overlap with this button + * @param JustPressed Whether the input (touch or mouse) was just pressed + * @param Pressed Whether the input (touch or mouse) is pressed + * @param Touch A FlxTouch, if this was called from an overlap with one + */ + private function updateStatus(Overlap:Bool, JustPressed:Bool, Pressed:Bool, ?Touch:FlxTouch):Void + { + if (Overlap) + { + if (JustPressed) + { + _pressedTouch = Touch; + if (Touch == null) + { + _pressedMouse = true; + } + onDownHandler(); + } + else if (status == FlxButton.NORMAL) + { + // Allow "swiping" to press a button (dragging it over the button while pressed) + if (allowSwiping && Pressed) + { + onDownHandler(); + } + else + { + onOverHandler(); + } + } + } + else if (status != FlxButton.NORMAL) + { + onOutHandler(); + } + + // onUp + #if !FLX_NO_TOUCH + if ((_pressedTouch != null) && _pressedTouch.justReleased) + { + onUpHandler(); + } + #end + } + + /** + * Using an event listener is necessary for security reasons on flash - + * certain things like opening a new window are only allowed when they are user-initiated. + */ + #if !FLX_NO_MOUSE + private function onUpEventListener(E:MouseEvent):Void + { + if (visible && exists && active && (status == FlxButton.PRESSED)) + { + onUpHandler(); + } + } + #end + + /** + * Internal function that handles the onUp event. + */ + private function onUpHandler():Void + { + status = FlxButton.NORMAL; + _pressedMouse = false; + _pressedTouch = null; + // Order matters here, because onUp.fire() could cause a state change and destroy this object. + onUp.fire(); + } + + /** + * Internal function that handles the onDown event. + */ + private function onDownHandler():Void + { + status = FlxButton.PRESSED; + // Order matters here, because onDown.fire() could cause a state change and destroy this object. + onDown.fire(); + } + + /** + * Internal function that handles the onOver event. + */ + private function onOverHandler():Void + { + status = FlxButton.HIGHLIGHT; + // Order matters here, because onOver.fire() could cause a state change and destroy this object. + onOver.fire(); + } + + /** + * Internal function that handles the onOut event. + */ + private function onOutHandler():Void + { + status = FlxButton.NORMAL; + // Order matters here, because onOut.fire() could cause a state change and destroy this object. + onOut.fire(); + } + + private function set_label(Value:T):T + { + if (Value != null) + { + // use the same FlxPoint object for both + Value.scrollFactor.put(); + Value.scrollFactor = scrollFactor; + } + return label = Value; + } + + private function set_status(Value:Int):Int + { + if ((labelAlphas.length > Value) && (label != null)) + { + label.alpha = alpha * labelAlphas[Value]; + } + return status = Value; + } + + override private function set_x(Value:Float):Float + { + super.set_x(Value); + + if (label != null) // Label positioning + { + label.x = x + labelOffsets[status].x; + } + return x; + } + + override private function set_y(Value:Float):Float + { + super.set_y(Value); + + if (label != null) // Label positioning + { + label.y = y + labelOffsets[status].y; + } + return y; + } +} + +/** + * Helper function for FlxButton which handles its events. + */ +private class FlxButtonEvent implements IFlxDestroyable +{ + /** + * The callback function to call when this even fires. + */ + public var callback:Void->Void; + + #if !FLX_NO_SOUND_SYSTEM + /** + * The sound to play when this event fires. + */ + public var sound:FlxSound; + #end + + /** + * @param Callback The callback function to call when this even fires. + * @param sound The sound to play when this event fires. + */ + public function new(?Callback:Void->Void, ?sound:FlxSound) + { + callback = Callback; + + #if !FLX_NO_SOUND_SYSTEM + this.sound = sound; + #end + } + + /** + * Cleans up memory. + */ + public inline function destroy():Void + { + callback = null; + + #if !FLX_NO_SOUND_SYSTEM + sound = FlxDestroyUtil.destroy(sound); + #end + } + + /** + * Fires this event (calls the callback and plays the sound) + */ + public inline function fire():Void + { + if (callback != null) + { + callback(); + } + + #if !FLX_NO_SOUND_SYSTEM + if (sound != null) + { + sound.play(true); + } + #end + } +} diff --git a/flixel/ui/FlxTypedButton.hx b/flixel/ui/FlxTypedButton.hx deleted file mode 100644 index 7642a53e54..0000000000 --- a/flixel/ui/FlxTypedButton.hx +++ /dev/null @@ -1,438 +0,0 @@ -package flixel.ui; - -import flash.display.BitmapData; -import flash.events.MouseEvent; -import flixel.FlxG; -import flixel.FlxSprite; -import flixel.input.touch.FlxTouch; -import flixel.system.FlxSound; -import flixel.util.FlxDestroyUtil; -import flixel.math.FlxPoint; - -@:bitmap("assets/images/ui/button.png") -private class GraphicButton extends BitmapData {} - -/** - * A simple button class that calls a function when clicked by the mouse. - */ -class FlxTypedButton extends FlxSprite -{ - /** - * The label that appears on the button. Can be any FlxSprite. - */ - public var label(default, set):T; - /** - * What offsets the label should have for each status. - */ - public var labelOffsets:Array; - /** - * What alpha value the label should have for each status. Default is [0.8, 1.0, 0.5]. - */ - public var labelAlphas:Array; - /** - * Whether you can press the button simply by releasing the touch / mouse button over it (default). - * If false, the input has to be pressed while hovering over the button. - */ - public var allowSwiping:Bool = true; - /** - * Whether to allow the HIHGLIGHT frame of the button graphic to be used on mobile - * (false by default, the NORMAL graphic is used instead then). - */ - public var allowHighlightOnMobile:Bool = false; - /** - * Shows the current state of the button, either FlxButton.NORMAL, - * FlxButton.HIGHLIGHT or FlxButton.PRESSED. - */ - public var status(default, set):Int; - /** - * The properties of this button's onUp event (callback function, sound). - */ - public var onUp(default, null):FlxButtonEvent; - /** - * The properties of this button's onDown event (callback function, sound). - */ - public var onDown(default, null):FlxButtonEvent; - /** - * The properties of this button's onOver event (callback function, sound). - */ - public var onOver(default, null):FlxButtonEvent; - /** - * The properties of this button's onOut event (callback function, sound). - */ - public var onOut(default, null):FlxButtonEvent; - - /** - * The touch currently pressing this button, if none, it's null. Needed to check for its release. - */ - private var _pressedTouch:FlxTouch; - /** - * Whether this button is currently being pressed by the mouse. Needed to check for its release. - */ - private var _pressedMouse:Bool = false; - - /** - * Creates a new FlxTypedButton object with a gray background. - * - * @param X The X position of the button. - * @param Y The Y position of the button. - * @param OnClick The function to call whenever the button is clicked. - */ - public function new(X:Float = 0, Y:Float = 0, ?OnClick:Void->Void) - { - super(X, Y); - - loadGraphic(GraphicButton, true, 80, 20); - - onUp = new FlxButtonEvent(OnClick); - onDown = new FlxButtonEvent(); - onOver = new FlxButtonEvent(); - onOut = new FlxButtonEvent(); - - labelAlphas = [0.8, 1.0, 0.5]; - labelOffsets = [FlxPoint.get(), FlxPoint.get(), FlxPoint.get(0, 1)]; - - status = FlxButton.NORMAL; - - // Since this is a UI element, the default scrollFactor is (0, 0) - scrollFactor.set(); - - #if !FLX_NO_MOUSE - FlxG.stage.addEventListener(MouseEvent.MOUSE_UP, onUpEventListener); - #end - } - - /** - * Called by the game state when state is changed (if this object belongs to the state) - */ - override public function destroy():Void - { - label = FlxDestroyUtil.destroy(label); - - onUp = FlxDestroyUtil.destroy(onUp); - onDown = FlxDestroyUtil.destroy(onDown); - onOver = FlxDestroyUtil.destroy(onOver); - onOut = FlxDestroyUtil.destroy(onOut); - - labelOffsets = FlxDestroyUtil.putArray(labelOffsets); - - labelAlphas = null; - _pressedTouch = null; - - #if !FLX_NO_MOUSE - FlxG.stage.removeEventListener(MouseEvent.MOUSE_UP, onUpEventListener); - #end - - super.destroy(); - } - - /** - * Called by the game loop automatically, handles mouseover and click detection. - */ - override public function update():Void - { - super.update(); - - if (!visible) - { - return; - } - - // Update the button, but only if at least either mouse or touches are enabled - #if (!FLX_NO_MOUSE || !FLX_NO_TOUCH) - updateButton(); - #end - - // Pick the appropriate animation frame - var nextFrame:Int = status; - - // "Highlight" doesn't make much sense on mobile devices / touchscreens - #if mobile - if (!allowHighlightOnMobile && (nextFrame == FlxButton.HIGHLIGHT)) - { - nextFrame = FlxButton.NORMAL; - } - #end - - animation.frameIndex = nextFrame; - } - - /** - * Just draws the button graphic and text label to the screen. - */ - override public function draw():Void - { - super.draw(); - - if (label != null && label.visible) - { - label.cameras = cameras; - label.draw(); - } - } - - #if !FLX_NO_DEBUG - /** - * Helper function to draw the debug graphic for the label as well. - */ - override public function drawDebug():Void - { - super.drawDebug(); - - if (label != null) - { - label.drawDebug(); - } - } - #end - - /** - * Basic button update logic - searches for overlaps with touches and - * the mouse cursor and calls updateStatus() - */ - private function updateButton():Void - { - // We're looking for any touch / mouse overlaps with this button - var overlapFound = false; - - // Have a look at all cameras - for (camera in cameras) - { - #if !FLX_NO_MOUSE - FlxG.mouse.getWorldPosition(camera, _point); - - if (overlapsPoint(_point, true, camera)) - { - overlapFound = true; - updateStatus(true, FlxG.mouse.justPressed, FlxG.mouse.pressed); - break; - } - #end - - #if !FLX_NO_TOUCH - for (touch in FlxG.touches.list) - { - touch.getWorldPosition(camera, _point); - - if (overlapsPoint(_point, true, camera)) - { - overlapFound = true; - updateStatus(true, touch.justPressed, touch.pressed, touch); - break; - } - } - #end - } - - if (!overlapFound) - { - updateStatus(false, false, false); - } - } - - /** - * Updates the button status by calling the respective event handler function. - * - * @param Overlap Whether there was any overlap with this button - * @param JustPressed Whether the input (touch or mouse) was just pressed - * @param Pressed Whether the input (touch or mouse) is pressed - * @param Touch A FlxTouch, if this was called from an overlap with one - */ - private function updateStatus(Overlap:Bool, JustPressed:Bool, Pressed:Bool, ?Touch:FlxTouch):Void - { - if (Overlap) - { - if (JustPressed) - { - _pressedTouch = Touch; - if (Touch == null) - { - _pressedMouse = true; - } - onDownHandler(); - } - else if (status == FlxButton.NORMAL) - { - // Allow "swiping" to press a button (dragging it over the button while pressed) - if (allowSwiping && Pressed) - { - onDownHandler(); - } - else - { - onOverHandler(); - } - } - } - else if (status != FlxButton.NORMAL) - { - onOutHandler(); - } - - // onUp - #if !FLX_NO_TOUCH - if ((_pressedTouch != null) && _pressedTouch.justReleased) - { - onUpHandler(); - } - #end - } - - /** - * Using an event listener is necessary for security reasons on flash - - * certain things like opening a new window are only allowed when they are user-initiated. - */ - #if !FLX_NO_MOUSE - private function onUpEventListener(E:MouseEvent):Void - { - if (visible && exists && active && (status == FlxButton.PRESSED)) - { - onUpHandler(); - } - } - #end - - /** - * Internal function that handles the onUp event. - */ - private function onUpHandler():Void - { - status = FlxButton.NORMAL; - _pressedMouse = false; - _pressedTouch = null; - // Order matters here, because onUp.fire() could cause a state change and destroy this object. - onUp.fire(); - } - - /** - * Internal function that handles the onDown event. - */ - private function onDownHandler():Void - { - status = FlxButton.PRESSED; - // Order matters here, because onDown.fire() could cause a state change and destroy this object. - onDown.fire(); - } - - /** - * Internal function that handles the onOver event. - */ - private function onOverHandler():Void - { - status = FlxButton.HIGHLIGHT; - // Order matters here, because onOver.fire() could cause a state change and destroy this object. - onOver.fire(); - } - - /** - * Internal function that handles the onOut event. - */ - private function onOutHandler():Void - { - status = FlxButton.NORMAL; - // Order matters here, because onOut.fire() could cause a state change and destroy this object. - onOut.fire(); - } - - private function set_label(Value:T):T - { - if (Value != null) - { - // use the same FlxPoint object for both - Value.scrollFactor.put(); - Value.scrollFactor = scrollFactor; - } - return label = Value; - } - - private function set_status(Value:Int):Int - { - if ((labelAlphas.length > Value) && (label != null)) - { - label.alpha = alpha * labelAlphas[Value]; - } - return status = Value; - } - - override private function set_x(Value:Float):Float - { - super.set_x(Value); - - if (label != null) // Label positioning - { - label.x = x + labelOffsets[status].x; - } - return x; - } - - override private function set_y(Value:Float):Float - { - super.set_y(Value); - - if (label != null) // Label positioning - { - label.y = y + labelOffsets[status].y; - } - return y; - } -} - -/** - * Helper function for FlxButton which handles its events. - */ -private class FlxButtonEvent implements IFlxDestroyable -{ - /** - * The callback function to call when this even fires. - */ - public var callback:Void->Void; - - #if !FLX_NO_SOUND_SYSTEM - /** - * The sound to play when this event fires. - */ - public var sound:FlxSound; - #end - - /** - * @param Callback The callback function to call when this even fires. - * @param sound The sound to play when this event fires. - */ - public function new(?Callback:Void->Void, ?sound:FlxSound) - { - callback = Callback; - - #if !FLX_NO_SOUND_SYSTEM - this.sound = sound; - #end - } - - /** - * Cleans up memory. - */ - public inline function destroy():Void - { - callback = null; - - #if !FLX_NO_SOUND_SYSTEM - sound = FlxDestroyUtil.destroy(sound); - #end - } - - /** - * Fires this event (calls the callback and plays the sound) - */ - public inline function fire():Void - { - if (callback != null) - { - callback(); - } - - #if !FLX_NO_SOUND_SYSTEM - if (sound != null) - { - sound.play(true); - } - #end - } -} diff --git a/flixel/ui/PxButton.hx b/flixel/ui/PxButton.hx index e2a5ff3428..ba999fbc4b 100644 --- a/flixel/ui/PxButton.hx +++ b/flixel/ui/PxButton.hx @@ -4,8 +4,8 @@ import flixel.system.FlxAssets; import flixel.text.FlxBitmapTextField; import flixel.text.pxText.PxBitmapFont; import flixel.text.pxText.PxTextAlign; -import flixel.ui.FlxTypedButton; import flixel.math.FlxPoint; +import flixel.ui.FlxButton; /** * A button with a bitmap text field for the label