diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5bbf83e17..a745be6cd 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -219,6 +219,12 @@ class PlayState extends MusicBeatSubState */ public var cameraFollowPoint:FlxObject; + /** + * An FlxTween that tweens the camera to the follow point. + * Only used when tweening the camera manually, rather than tweening via follow. + */ + public var cameraFollowTween:FlxTween; + /** * The camera follow point from the last stage. * Used to persist the position of the `cameraFollowPosition` between levels. @@ -2847,15 +2853,51 @@ class PlayState extends MusicBeatSubState /** * Resets the camera's zoom level and focus point. */ - public function resetCamera():Void + public function resetCamera(?resetZoom:Bool = true, ?cancelFollowTween:Bool = true):Void { + // Cancel the follow tween if it's active. + if (cancelFollowTween && cameraFollowTween != null) + { + cameraFollowTween.cancel(); + } + FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); FlxG.camera.targetOffset.set(); - FlxG.camera.zoom = defaultCameraZoom; + + if (resetZoom) + { + FlxG.camera.zoom = defaultCameraZoom; + } + // Snap the camera to the follow point immediately. FlxG.camera.focusOn(cameraFollowPoint.getPosition()); } + /** + * Disables camera following and tweens the camera to the follow point manually. + */ + public function tweenCamera(?duration:Float, ?ease:NullFloat>):Void + { + // Cancel the current tween if it's active. + if (cameraFollowTween != null) + { + cameraFollowTween.cancel(); + } + + // Disable camera following for the duration of the tween. + FlxG.camera.target = null; + + // Follow tween! Caching it so we can cancel it later if needed. + var followPos:FlxPoint = cameraFollowPoint.getPosition() - FlxPoint.weak(FlxG.camera.width * 0.5, FlxG.camera.height * 0.5); + cameraFollowTween = FlxTween.tween(FlxG.camera.scroll, {x: followPos.x, y: followPos.y}, duration, + { + ease: ease, + onComplete: function(_) { + resetCamera(false, false); // Re-enable camera following when the tween is complete. + } + }); + } + #if (debug || FORCE_DEBUG_VERSION) /** * Jumps forward or backward a number of sections in the song. diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 847df4a60..28a629f1a 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -1,5 +1,6 @@ package funkin.play.event; +import flixel.tweens.FlxEase; // Data from the chart import funkin.data.song.SongData; import funkin.data.song.SongData.SongEventData; @@ -66,6 +67,13 @@ class FocusCameraSongEvent extends SongEvent if (char == null) char = cast data.value; + var useTween:Null = data.getBool('useTween'); + if (useTween == null) useTween = false; + var duration:Null = data.getFloat('duration'); + if (duration == null) duration = 4.0; + var ease:Null = data.getString('ease'); + if (ease == null) ease = 'linear'; + switch (char) { case -1: // Position @@ -114,6 +122,20 @@ class FocusCameraSongEvent extends SongEvent default: trace('Unknown camera focus: ' + data); } + + if (useTween) // always ends up false?? + { + var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; + + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); + if (easeFunction == null) + { + trace('Invalid ease function: $ease'); + return; + } + + PlayState.instance.tweenCamera(durSeconds, easeFunction); + } } public override function getTitle():String @@ -155,6 +177,51 @@ class FocusCameraSongEvent extends SongEvent step: 10.0, type: SongEventFieldType.FLOAT, units: "px" + }, + { + name: 'useTween', + title: 'Use Tween', + type: SongEventFieldType.BOOL, + defaultValue: false + }, + { + name: 'duration', + title: 'Duration', + defaultValue: 4.0, + step: 0.5, + type: SongEventFieldType.FLOAT, + units: 'steps' + }, + { + name: 'ease', + title: 'Easing Type', + defaultValue: 'linear', + type: SongEventFieldType.ENUM, + keys: [ + 'Linear' => 'linear', + 'Instant' => 'INSTANT', + 'Quad In' => 'quadIn', + 'Quad Out' => 'quadOut', + 'Quad In/Out' => 'quadInOut', + 'Cube In' => 'cubeIn', + 'Cube Out' => 'cubeOut', + 'Cube In/Out' => 'cubeInOut', + 'Quart In' => 'quartIn', + 'Quart Out' => 'quartOut', + 'Quart In/Out' => 'quartInOut', + 'Quint In' => 'quintIn', + 'Quint Out' => 'quintOut', + 'Quint In/Out' => 'quintInOut', + 'Smooth Step In' => 'smoothStepIn', + 'Smooth Step Out' => 'smoothStepOut', + 'Smooth Step In/Out' => 'smoothStepInOut', + 'Sine In' => 'sineIn', + 'Sine Out' => 'sineOut', + 'Sine In/Out' => 'sineInOut', + 'Elastic In' => 'elasticIn', + 'Elastic Out' => 'elasticOut', + 'Elastic In/Out' => 'elasticInOut', + ] } ]); }