Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add FlxTween.flicker #3086

Merged
merged 6 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 107 additions & 17 deletions flixel/tweens/FlxTween.hx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import flixel.math.FlxMath;
import flixel.tweens.FlxEase.EaseFunction;
import flixel.tweens.misc.AngleTween;
import flixel.tweens.misc.ColorTween;
import flixel.tweens.misc.FlickerTween;
import flixel.tweens.misc.NumTween;
import flixel.tweens.misc.VarTween;
import flixel.tweens.motion.CircularMotion;
Expand Down Expand Up @@ -252,7 +253,39 @@ class FlxTween implements IFlxDestroyable
{
return globalManager.num(FromValue, ToValue, Duration, Options, TweenFunction);
}


/**
* Flickers the desired object
*
* @param basic The object to flicker
* @param duration Duration of the tween, in seconds
* @param period How often, in seconds, the visibility cycles
* @param options A structure with flicker and tween options
* @since 5.7.0
*/
public static function flicker(basic:FlxBasic, duration = 1.0, period = 0.08, ?options:FlickerTweenOptions)
{
return globalManager.flicker(basic, duration, period, options);
}

/**
* Whether the object is flickering via the global tween manager
* @since 5.7.0
*/
public static function isFlickering(basic:FlxBasic)
{
return globalManager.isFlickering(basic);
}

/**
* Cancels all flicker tweens on the object in the global tween manager
* @since 5.7.0
*/
public static function stopFlickering(basic:FlxBasic)
{
return globalManager.stopFlickering(basic);
}

/**
* A simple shake effect for FlxSprite. Shorthand for creating a ShakeTween, starting it and adding it to the TweenManager.
*
Expand Down Expand Up @@ -517,6 +550,12 @@ class FlxTween implements IFlxDestroyable
public var finished(default, null):Bool;
public var scale(default, null):Float = 0;
public var backward(default, null):Bool;

/**
* The total time passed since start
* @since 5.7.0
*/
public var time(get, never):Float;

/**
* How many times this tween has been executed / has finished so far - useful to
Expand Down Expand Up @@ -854,10 +893,15 @@ class FlxTween implements IFlxDestroyable
}
return loopDelay = dly;
}

inline function get_time():Float
{
return Math.max(_secondsSinceStart - _delayToUse, 0);
}

inline function get_percent():Float
{
return Math.max((_secondsSinceStart - _delayToUse), 0) / duration;
return time / duration;
}

function set_percent(value:Float):Float
Expand Down Expand Up @@ -999,6 +1043,40 @@ class FlxTweenManager extends FlxBasic
tween.tween(FromValue, ToValue, Duration, TweenFunction);
return add(tween);
}

/**
* Flickers the desired object
*
* @param basic The object to flicker
* @param duration Duration of the tween, in seconds
* @param period How often, in seconds, the visibility cycles
* @param options A structure with flicker and tween options
* @since 5.7.0
*/
public function flicker(basic:FlxBasic, duration = 1.0, period = 0.08, ?options:FlickerTweenOptions)
{
final tween = new FlickerTween(options, this);
tween.tween(basic, duration, period);
return add(tween);
}

/**
* Whether the object is flickering via this manager
* @since 5.7.0
*/
public function isFlickering(basic:FlxBasic)
{
return containsTweensOf(basic, ["flicker"]);
}

/**
* Cancels all flicker tweens on the object
* @since 5.7.0
*/
public function stopFlickering(basic:FlxBasic)
{
return cancelTweensOf(basic, ["flicker"]);
}

/**
* A simple shake effect for FlxSprite. Shorthand for creating a ShakeTween, starting it and adding it to the TweenManager.
Expand Down Expand Up @@ -1394,37 +1472,37 @@ class FlxTweenManager extends FlxBasic
*
* Note: loops backwards to allow removals.
*
* @param Object The object with tweens you are searching for.
* @param FieldPaths Optional list of the tween field paths to check. If null or empty, any tween of the specified
* object will match. Allows dot paths to check child properties.
* @param Function The function to call on each matching tween.
* @param object The object with tweens you are searching for.
* @param fieldPaths List of the tween field paths to check. If `null` or empty, any tween of
* the specified object will match. Allows dot paths to check child properties.
* @param func The function to call on each matching tween.
*
* @since 4.9.0
*/
function forEachTweensOf(Object:Dynamic, ?FieldPaths:Array<String>, Function:FlxTween->Void)
function forEachTweensOf(object:Dynamic, ?fieldPaths:Array<String>, func:FlxTween->Void)
{
if (Object == null)
if (object == null)
throw "Cannot cancel tween variables of an object that is null.";

if (FieldPaths == null || FieldPaths.length == 0)
if (fieldPaths == null || fieldPaths.length == 0)
{
var i = _tweens.length;
while (i-- > 0)
{
var tween = _tweens[i];
if (tween.isTweenOf(Object))
Function(tween);
if (tween.isTweenOf(object))
func(tween);
}
}
else
{
// check for dot paths and convert to object/field pairs
var propertyInfos = new Array<TweenProperty>();
for (fieldPath in FieldPaths)
for (fieldPath in fieldPaths)
{
var target = Object;
var path = fieldPath.split(".");
var field = path.pop();
var target = object;
final path = fieldPath.split(".");
final field = path.pop();
for (component in path)
{
target = Reflect.getProperty(target, component);
Expand All @@ -1439,18 +1517,30 @@ class FlxTweenManager extends FlxBasic
var i = _tweens.length;
while (i-- > 0)
{
var tween = _tweens[i];
final tween = _tweens[i];
for (info in propertyInfos)
{
if (tween.isTweenOf(info.object, info.field))
{
Function(tween);
func(tween);
break;
}
}
}
}
}

/**
* Crude helper to search for any tweens with the desired properties
*
* @since 5.7.0
*/
function containsTweensOf(object:Dynamic, ?fieldPaths:Array<String>):Bool
{
var found = false;
forEachTweensOf(object, fieldPaths, (_)->found = true);
return found;
}

/**
* Immediately updates all tweens that are not looping (type `FlxTween.LOOPING` or `FlxTween.PINGPONG`)
Expand Down
130 changes: 130 additions & 0 deletions flixel/tweens/misc/FlickerTween.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package flixel.tweens.misc;

import flixel.FlxBasic;
import flixel.tweens.FlxTween;

/**
* Special tween options for flicker tweens
* @since 5.7.0
*/
typedef FlickerTweenOptions = TweenOptions &
{
/**
* Whether the object will show after the tween, defaults to `true`
*/
?endVisibility:Bool,

/**
* The amount of time the object will show, compared to the total duration, The default is `0.5`,
* meaning equal times visible and invisible.
*/
?ratio:Float,

/**
* An optional custom flicker function, defaults to
* `function (tween) { return (tween.time / tween.period) % 1 > tween.ratio; }`
*/
?tweenFunction:(FlickerTween)->Bool
};

/**
* Flickers an object. See `FlxTween.flicker()`
* @since 5.7.0
*/
class FlickerTween extends FlxTween
{
/** The object being flickered */
public var basic(default, null):FlxBasic;

/** Controls how the object flickers over time */
public var tweenFunction(default, null):(FlickerTween)->Bool;

/** Whether the object will show after the tween, defaults to `true` */
public var endVisibility(default, null):Bool = true;

/** How often, in seconds, the visibility cycles */
public var period(default, null):Float = 0.08;

/**
* The ratio of time the object will show, default is `0.5`,
* meaning equal times visible and invisible.
*/
public var ratio(default, null):Float = 0.5;

function new(options:FlickerTweenOptions, ?manager:FlxTweenManager):Void
{
tweenFunction = defaultTweenFunction;
if (options != null)
{
if (options.endVisibility != null)
endVisibility = options.endVisibility;

if (options.ratio != null)
ratio = options.ratio;

if (options.tweenFunction != null)
tweenFunction = options.tweenFunction;
}

super(options, manager);
}

/**
* Clean up references
*/
override function destroy()
{
super.destroy();
basic = null;
}

/**
* Flickers the desired object
*
* @param basic The object to flicker
* @param duration Duration of the tween, in seconds
* @param period How often, in seconds, the visibility cycles
*/
public function tween(basic:FlxBasic, duration:Float, period:Float):FlickerTween
{
this.basic = basic;
this.duration = duration;

start();
return this;
}

override function update(elapsed:Float):Void
{
super.update(elapsed);

if (tweenFunction != null && _secondsSinceStart >= _delayToUse)
{
final visible = tweenFunction(this);
// do not call setter every frame
if (basic.visible != visible)
basic.visible = visible;
}
}

override function onEnd()
{
super.onEnd();

basic.visible = endVisibility;
}

override function isTweenOf(object:Dynamic, ?field:String):Bool
{
return basic == object && (field == null || field == "visible" || field == "flicker");
}

/**
* The default tween function of flicker tweens
* @param tween The tween handling the flickering
*/
public static function defaultTweenFunction(tween:FlickerTween)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the functions here have explicit returns types on the signature. What is the flixel guideline for return types in function signatures?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no rule on this, I kinda prefer not having it, in more cases, especially here where tweenFunction is well defined

{
return (tween.time / tween.period) % 1 > tween.ratio;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was curious how you approached handling the ratio. This is pleasantly simple 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at first I had > 0.5 then i wondered what other values would look like

}
}
Loading