1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-09-01 03:15:53 +00:00
Funkin/source/funkin/ui/title/AttractState.hx
2025-08-17 03:37:38 +08:00

207 lines
4.9 KiB
Haxe

package funkin.ui.title;
#if html5
import funkin.graphics.video.FlxVideo;
#end
#if hxvlc
import funkin.graphics.video.FunkinVideoSprite;
#end
#if FEATURE_TOUCH_CONTROLS
import funkin.util.TouchUtil;
#end
import funkin.ui.MusicBeatState;
import funkin.ui.FullScreenScaleMode;
import flixel.FlxG;
import flixel.math.FlxMath;
import flixel.util.FlxColor;
import flixel.addons.display.FlxPieDial;
/**
* After 40 seconds of inactivity on the title screen,
* the game will enter the Attract state, as a reference to physical arcade machines.
*
* In the current version, this just plays generic game/merch trailers,
* but this can be updated to include gameplay footage, or something more elaborate.
*/
class AttractState extends MusicBeatState
{
/**
* The videos that can be played by the Attract state.
* @param path The path to the video to play.
* This used
*/
static final VIDEO_PATHS:Array<{path:String}> = [
{path: Paths.videos('mobileRelease')},
{path: Paths.videos('boyfriendEverywhere')},
];
static var nextVideoToPlay:Int = 0;
/**
* Duration you need to touch for to skip the video.
*/
static final HOLD_TIME:Float = 1.5;
var pie:FlxPieDial;
var holdDelta:Float = 0;
public override function create():Void
{
// Pause existing music.
if (FlxG.sound.music != null)
{
FlxG.sound.music.destroy();
FlxG.sound.music = null;
}
#if html5
var videoPath:String = getVideoPath();
trace('Playing web video ${videoPath}');
playVideoHTML5(videoPath);
#end
#if hxvlc
var videoPath:String = getVideoPath();
trace('Playing native video ${videoPath}');
playVideoNative(videoPath);
#end
pie = new FlxPieDial(0, 0, 40, FlxColor.WHITE, 45, CIRCLE, true, 20);
pie.x = FlxG.width - ((pie.width * 1.5) + FullScreenScaleMode.gameNotchSize.x);
pie.y = FlxG.height - (pie.height * 1.5);
pie.amount = 0;
pie.replaceColor(FlxColor.BLACK, 0x8AC5C4C4);
add(pie);
}
/**
* Get the path of a random video to display to the user.
* @return The video path to play.
*/
function getVideoPath():String
{
var result:String = VIDEO_PATHS[nextVideoToPlay].path;
nextVideoToPlay = (nextVideoToPlay + 1) % VIDEO_PATHS.length;
#if html5
result = Paths.stripLibrary(result);
#end
return result;
}
#if html5
var vid:FlxVideo;
function playVideoHTML5(filePath:String):Void
{
// Video displays OVER the FlxState.
vid = new FlxVideo(filePath);
if (vid != null)
{
vid.zIndex = 0;
vid.finishCallback = onAttractEnd;
add(vid);
}
else
{
trace('ALERT: Video is null! Could not play cutscene!');
}
}
#end
#if hxvlc
var vid:FunkinVideoSprite;
function playVideoNative(filePath:String):Void
{
// Video displays OVER the FlxState.
vid = new FunkinVideoSprite(0, 0);
if (vid != null)
{
vid.zIndex = 0;
vid.active = false;
vid.bitmap.onEncounteredError.add(function(msg:String):Void {
trace('[VLC] Encountered an error: $msg');
onAttractEnd();
});
vid.bitmap.onEndReached.add(onAttractEnd);
vid.bitmap.onFormatSetup.add(() -> {
vid.setGraphicSize(FlxG.initialWidth, FlxG.initialHeight);
vid.updateHitbox();
vid.screenCenter();
});
add(vid);
if (vid.load(filePath)) vid.play();
}
else
{
trace('ALERT: Video is null! Could not play cutscene!');
}
}
#end
public override function update(elapsed:Float):Void
{
super.update(elapsed);
// If the user presses any button or hold their screen for 1.5 seconds, skip the video.
if ((FlxG.keys.pressed.ANY && !controls.VOLUME_MUTE && !controls.VOLUME_UP && !controls.VOLUME_DOWN) #if FEATURE_TOUCH_CONTROLS
|| TouchUtil.touch != null && TouchUtil.touch.pressed #end)
{
holdDelta += elapsed;
holdDelta = holdDelta.clamp(0, HOLD_TIME);
pie.scale.x = pie.scale.y = FlxMath.lerp(pie.scale.x, 1.3, Math.exp(-elapsed * 140.0));
}
else
{
holdDelta = FlxMath.lerp(holdDelta, -0.1, (elapsed * 3).clamp(0, 1));
holdDelta = holdDelta.clamp(0, HOLD_TIME);
pie.scale.x = pie.scale.y = FlxMath.lerp(pie.scale.x, 1, Math.exp(-elapsed * 160.0));
}
pie.amount = Math.min(1, Math.max(0, (holdDelta / HOLD_TIME) * 1.025));
pie.alpha = FlxMath.remapToRange(pie.amount, 0.025, 1, 0, 1);
// If the dial is full, skip the video.
if (pie.amount >= 1) onAttractEnd();
}
/**
* When the attraction state ends (after the video ends or the user presses any button),
* switch immediately to the title screen.
*/
function onAttractEnd():Void
{
#if html5
if (vid != null)
{
remove(vid);
}
#end
#if hxvlc
if (vid != null)
{
vid.stop();
remove(vid);
}
#end
#if (html5 || hxvlc)
vid.destroy();
vid = null;
#end
FlxG.switchState(() -> new TitleState());
}
}