From 858d8edf7aba344539317754e0e3e7dbefacb4e0 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Tue, 27 Feb 2024 23:29:40 -0700 Subject: [PATCH 1/7] Cam tweening working! UI buggy af. To fix. --- source/funkin/play/PlayState.hx | 46 ++++++++++++- .../funkin/play/event/FocusCameraSongEvent.hx | 67 +++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) 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', + ] } ]); } From d9cf097e460de497a7a6754c85eade23b39b9aa3 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Mon, 4 Mar 2024 20:57:21 -0700 Subject: [PATCH 2/7] Fixed bools and associated checkboxes not updating properly. --- source/funkin/play/event/FocusCameraSongEvent.hx | 2 +- .../debug/charting/toolboxes/ChartEditorEventDataToolbox.hx | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 28a629f1a..979b1ad7b 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -123,7 +123,7 @@ class FocusCameraSongEvent extends SongEvent trace('Unknown camera focus: ' + data); } - if (useTween) // always ends up false?? + if (useTween) { var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index ec46e1f85..50b341272 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -237,6 +237,11 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox { value = event.target.value.value; } + else if (field.type == BOOL) + { + var chk:CheckBox = cast event.target; + value = cast(chk.selected, Null); // Need to cast to nullable bool or the compiler will get mad. + } trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); From 21b895ab9735ca1c121026326ab5ab2a0c4c2e24 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Fri, 8 Mar 2024 14:50:26 -0700 Subject: [PATCH 3/7] Assets submod update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 55c602f2a..49c409b4c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 55c602f2adbbd84de541ea86e5e646c4d2a1df0b +Subproject commit 49c409b4c8321d8cd317787a78c479aaa64cb517 From 6b8fb7dc77a9c11ecc5afa541e05d004c64aaca2 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Sun, 10 Mar 2024 16:35:41 -0700 Subject: [PATCH 4/7] Standardized camera zoom tweening to match camera follow tweening. Implemented methods to cancel tweens in necessary places. Start of pausing tweens when pausing the game (WIP). (CHANGES NOT TESTED EXPECT SOMETHING TO BREAK) --- source/funkin/play/GameOverSubState.hx | 9 +- source/funkin/play/PlayState.hx | 124 +++++++++++++++--- .../funkin/play/event/FocusCameraSongEvent.hx | 22 ++-- .../funkin/play/event/ZoomCameraSongEvent.hx | 8 +- .../ui/debug/stage/StageOffsetSubState.hx | 11 +- 5 files changed, 136 insertions(+), 38 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 95304d762..b3e815a41 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -119,6 +119,8 @@ class GameOverSubState extends MusicBeatSubState // Set up the visuals // + var playState = PlayState.instance; + // Add a black background to the screen. var bg = new FunkinSprite().makeSolidColor(FlxG.width * 2, FlxG.height * 2, FlxColor.BLACK); // We make this transparent so that we can see the stage underneath during debugging, @@ -130,13 +132,16 @@ class GameOverSubState extends MusicBeatSubState // Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubState. // We can then play the character's `firstDeath` animation. - boyfriend = PlayState.instance.currentStage.getBoyfriend(true); + boyfriend = playState.currentStage.getBoyfriend(true); boyfriend.isDead = true; add(boyfriend); boyfriend.resetCharacter(); + // Cancel camera tweening if it's currently active. + playState.cancelAllCameraTweens(); + // Assign a camera follow point to the boyfriend's position. - cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); + cameraFollowPoint = new FlxObject(playState.cameraFollowPoint.x, playState.cameraFollowPoint.y, 1, 1); cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; var offsets:Array = boyfriend.getDeathCameraOffsets(); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3ce1f4948..0bd731bc6 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -242,6 +242,11 @@ class PlayState extends MusicBeatSubState */ public var cameraFollowTween:FlxTween; + /** + * An FlxTween that zooms the camera to the desired amount. + */ + public var cameraZoomTween:FlxTween; + /** * The camera follow point from the last stage. * Used to persist the position of the `cameraFollowPosition` between levels. @@ -402,10 +407,12 @@ class PlayState extends MusicBeatSubState var startingSong:Bool = false; /** - * False if `FlxG.sound.music` + * Track if we currently have the music paused for a Pause substate, so we can unpause it when we return. */ var musicPausedBySubState:Bool = false; + var cameraFollowTweenPausedBySubState:Bool = false; // Idea FOR LATER: Store paused tweens in an array, so we know which ones to unpause when leaving the Pause substate. + /** * False until `create()` has completed. */ @@ -1126,14 +1133,24 @@ class PlayState extends MusicBeatSubState // Pause the music. if (FlxG.sound.music != null) { - musicPausedBySubState = FlxG.sound.music.playing; - if (musicPausedBySubState) + if (FlxG.sound.music.playing) { FlxG.sound.music.pause(); + musicPausedBySubState = true; } + + // Pause vocals. + // Not tracking that we've done this via a bool because vocal re-syncing involves pausing the vocals anyway. if (vocals != null) vocals.pause(); } + // Pause camera tweening. + if (cameraFollowTween != null && cameraFollowTween.active) + { + cameraFollowTween.active = false; + cameraFollowTweenPausedBySubState = true; + } + // Pause the countdown. Countdown.pauseCountdown(); } @@ -1155,10 +1172,18 @@ class PlayState extends MusicBeatSubState if (event.eventCanceled) return; - // Resume + // Resume music if we paused it. if (musicPausedBySubState) { FlxG.sound.music.play(); + musicPausedBySubState = false; + } + + // Resume camera tweening if we paused it. + if (cameraFollowTweenPausedBySubState) + { + cameraFollowTween.active = true; + cameraFollowTweenPausedBySubState = false; } if (currentConversation != null) @@ -1166,6 +1191,7 @@ class PlayState extends MusicBeatSubState currentConversation.resumeMusic(); } + // Re-sync vocals. if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. @@ -2852,6 +2878,9 @@ class PlayState extends MusicBeatSubState */ function performCleanup():Void { + // If the camera is being tweened, stop it. + cancelAllCameraTweens(); + if (currentConversation != null) { remove(currentConversation); @@ -2910,6 +2939,9 @@ class PlayState extends MusicBeatSubState // Stop camera zooming on beat. cameraZoomRate = 0; + // Cancel camera tweening if it's active. + cancelAllCameraTweens(); + // If the opponent is GF, zoom in on the opponent. // Else, if there is no GF, zoom in on BF. // Else, zoom in on GF. @@ -2996,12 +3028,12 @@ class PlayState extends MusicBeatSubState /** * Resets the camera's zoom level and focus point. */ - public function resetCamera(?resetZoom:Bool = true, ?cancelFollowTween:Bool = true):Void + public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true):Void { - // Cancel the follow tween if it's active. - if (cancelFollowTween && cameraFollowTween != null) + // Cancel camera tweens if any are active. + if (cancelTweens) { - cameraFollowTween.cancel(); + cancelAllCameraTweens(); } FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); @@ -3019,26 +3051,78 @@ class PlayState extends MusicBeatSubState /** * Disables camera following and tweens the camera to the follow point manually. */ - public function tweenCamera(?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraToFollowPoint(?duration:Float, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. + cancelCameraFollowTween(); + + if (duration == 0) + { + // Instant movement. Just reset the camera to force it to the follow point. + resetCamera(false, false); + } + else + { + // Disable camera following for the duration of the tween. + FlxG.camera.target = null; + + // Follow tween! Caching it so we can cancel/pause 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. + } + }); + } + } + + public function cancelCameraFollowTween() + { if (cameraFollowTween != null) { cameraFollowTween.cancel(); } + } - // Disable camera following for the duration of the tween. - FlxG.camera.target = null; + /** + * Tweens the camera zoom to the desired amount. Tweens defaultCameraZoom to avoid breaking camera bops. + */ + public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?ease:NullFloat>):Void + { + // Cancel the current tween if it's active. + cancelCameraZoomTween(); - // 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. - } - }); + var targetZoom = zoom * FlxCamera.defaultZoom; + + if (duration == 0) + { + // Instant zoom. No tween needed. + defaultCameraZoom = targetZoom; + } + else + { + // Zoom tween! Caching it so we can cancel/pause it later if needed. + cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease}); + } + } + + public function cancelCameraZoomTween() + { + if (cameraZoomTween != null) + { + cameraZoomTween.cancel(); + } + } + + /** + * Cancel all active camera tweens simultaneously. + */ + public function cancelAllCameraTweens() + { + cancelCameraFollowTween(); + cancelCameraZoomTween(); } #if (debug || FORCE_DEBUG_VERSION) diff --git a/source/funkin/play/event/FocusCameraSongEvent.hx b/source/funkin/play/event/FocusCameraSongEvent.hx index 979b1ad7b..cd4366dd2 100644 --- a/source/funkin/play/event/FocusCameraSongEvent.hx +++ b/source/funkin/play/event/FocusCameraSongEvent.hx @@ -125,16 +125,22 @@ class FocusCameraSongEvent extends SongEvent if (useTween) { - var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; - - var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); - if (easeFunction == null) + switch (ease) { - trace('Invalid ease function: $ease'); - return; - } + case 'INSTANT': + PlayState.instance.tweenCameraToFollowPoint(0); + default: + var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; - PlayState.instance.tweenCamera(durSeconds, easeFunction); + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); + if (easeFunction == null) + { + trace('Invalid ease function: $ease'); + return; + } + + PlayState.instance.tweenCameraToFollowPoint(durSeconds, easeFunction); + } } } diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index 809130499..187664e97 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -69,9 +69,10 @@ class ZoomCameraSongEvent extends SongEvent switch (ease) { case 'INSTANT': - // Set the zoom. Use defaultCameraZoom to prevent breaking camera bops. - PlayState.instance.defaultCameraZoom = zoom * FlxCamera.defaultZoom; + PlayState.instance.tweenCameraZoom(zoom, 0); default: + var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; + var easeFunction:NullFloat> = Reflect.field(FlxEase, ease); if (easeFunction == null) { @@ -79,8 +80,7 @@ class ZoomCameraSongEvent extends SongEvent return; } - FlxTween.tween(PlayState.instance, {defaultCameraZoom: zoom * FlxCamera.defaultZoom}, (Conductor.instance.stepLengthMs * duration / 1000), - {ease: easeFunction}); + PlayState.instance.tweenCameraZoom(zoom, durSeconds, easeFunction); } } diff --git a/source/funkin/ui/debug/stage/StageOffsetSubState.hx b/source/funkin/ui/debug/stage/StageOffsetSubState.hx index e8a5d0a23..fa5056220 100644 --- a/source/funkin/ui/debug/stage/StageOffsetSubState.hx +++ b/source/funkin/ui/debug/stage/StageOffsetSubState.hx @@ -49,8 +49,11 @@ class StageOffsetSubState extends HaxeUISubState { super.create(); + var playState = PlayState.instance; + FlxG.mouse.visible = true; - PlayState.instance.pauseMusic(); + playState.pauseMusic(); + playState.cancelAllCameraTweens(); FlxG.camera.target = null; setupUIListeners(); @@ -63,8 +66,8 @@ class StageOffsetSubState extends HaxeUISubState // add(uiStuff); - PlayState.instance.persistentUpdate = true; - component.cameras = [PlayState.instance.camHUD]; + playState.persistentUpdate = true; + component.cameras = [playState.camHUD]; // uiStuff.cameras = [PlayState.instance.camHUD]; // btn.cameras = [PlayState.instance.camHUD]; @@ -72,7 +75,7 @@ class StageOffsetSubState extends HaxeUISubState var layerList:ListView = findComponent("prop-layers"); - for (thing in PlayState.instance.currentStage) + for (thing in playState.currentStage) { var prop:StageProp = cast thing; if (prop != null && prop.name != null) From 1541f0aa68478d0651c8ba2fccc181e75883e964 Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Fri, 15 Mar 2024 01:52:22 -0700 Subject: [PATCH 5/7] Camera tween pausing/unpausing additions --- assets | 2 +- source/funkin/play/PlayState.hx | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/assets b/assets index 0e2c5bf21..223722892 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0e2c5bf2134c7e517b70cf74afd58abe5c7b5e50 +Subproject commit 2237228923c6bd35df1e68e3b2a13dfffd1c243d diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 7c0ed64b3..d6cb5bd47 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -412,7 +412,10 @@ class PlayState extends MusicBeatSubState */ var musicPausedBySubState:Bool = false; - var cameraFollowTweenPausedBySubState:Bool = false; // Idea FOR LATER: Store paused tweens in an array, so we know which ones to unpause when leaving the Pause substate. + /** + * Track any camera tweens we've paused for a Pause substate, so we can unpause them when we return. + */ + var cameraTweensPausedBySubState:List = new List(); /** * False until `create()` has completed. @@ -1145,11 +1148,17 @@ class PlayState extends MusicBeatSubState if (vocals != null) vocals.pause(); } - // Pause camera tweening. + // Pause camera tweening, and keep track of which tweens we pause. if (cameraFollowTween != null && cameraFollowTween.active) { cameraFollowTween.active = false; - cameraFollowTweenPausedBySubState = true; + cameraTweensPausedBySubState.add(cameraFollowTween); + } + + if (cameraZoomTween != null && cameraZoomTween.active) + { + cameraZoomTween.active = false; + cameraTweensPausedBySubState.add(cameraZoomTween); } // Pause the countdown. @@ -1180,12 +1189,12 @@ class PlayState extends MusicBeatSubState musicPausedBySubState = false; } - // Resume camera tweening if we paused it. - if (cameraFollowTweenPausedBySubState) + // Resume camera tweens if we paused any. + for (camTween in cameraTweensPausedBySubState) { - cameraFollowTween.active = true; - cameraFollowTweenPausedBySubState = false; + camTween.active = true; } + cameraTweensPausedBySubState.clear(); if (currentConversation != null) { From 494a3c9e86dbc6ddb71a193488c3023c26558d9a Mon Sep 17 00:00:00 2001 From: Jenny Crowe Date: Sat, 16 Mar 2024 08:38:10 -0700 Subject: [PATCH 6/7] Bugfixes. New additive zoom mode for camera tweening. --- source/funkin/play/PlayState.hx | 64 ++++++++++++++----- .../funkin/play/event/ZoomCameraSongEvent.hx | 22 +++++-- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d6cb5bd47..a5152e727 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -255,14 +255,23 @@ class PlayState extends MusicBeatSubState public var previousCameraFollowPoint:FlxPoint = null; /** - * The current camera zoom level. - * - * The camera zoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect. - * Defaults to 1.05 but may be larger or smaller depending on the current stage, - * and may be changed by the `ZoomCamera` song event. + * The current camera zoom level without any modifiers applied. + */ + public var currentCameraZoom:Float = FlxCamera.defaultZoom * 1.05; + + /** + * currentCameraZoom is increased every beat, and lerped back to this value every frame, creating a smooth 'zoom-in' effect. + * Defaults to 1.05, but may be larger or smaller depending on the current stage. + * Tweened via the `ZoomCamera` song event in direct mode. */ public var defaultCameraZoom:Float = FlxCamera.defaultZoom * 1.05; + /** + * Camera zoom applied on top of currentCameraZoom. + * Tweened via the `ZoomCamera` song event in additive mode. + */ + public var additiveCameraZoom:Float = 0; + /** * The current HUD camera zoom level. * @@ -959,7 +968,9 @@ class PlayState extends MusicBeatSubState // Lerp the camera zoom towards the target level. if (subState == null) { - FlxG.camera.zoom = FlxMath.lerp(defaultCameraZoom, FlxG.camera.zoom, 0.95); + currentCameraZoom = FlxMath.lerp(defaultCameraZoom, currentCameraZoom, 0.95); + FlxG.camera.zoom = currentCameraZoom + additiveCameraZoom; + camHUD.zoom = FlxMath.lerp(defaultHUDCameraZoom, camHUD.zoom, 0.95); } @@ -1349,7 +1360,7 @@ class PlayState extends MusicBeatSubState if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0) { // Zoom camera in (1.5%) - FlxG.camera.zoom += cameraZoomIntensity * defaultCameraZoom; + currentCameraZoom += cameraZoomIntensity * defaultCameraZoom; // Hud zooms double (3%) camHUD.zoom += hudCameraZoomIntensity * defaultHUDCameraZoom; } @@ -1541,6 +1552,11 @@ class PlayState extends MusicBeatSubState { // Apply camera zoom level from stage data. defaultCameraZoom = currentStage.camZoom; + currentCameraZoom = defaultCameraZoom; + FlxG.camera.zoom = currentCameraZoom; + + // Reset additive zoom. + additiveCameraZoom = 0; } /** @@ -3051,7 +3067,7 @@ class PlayState extends MusicBeatSubState if (resetZoom) { - FlxG.camera.zoom = defaultCameraZoom; + resetCameraZoom(); } // Snap the camera to the follow point immediately. @@ -3097,24 +3113,40 @@ class PlayState extends MusicBeatSubState } /** - * Tweens the camera zoom to the desired amount. Tweens defaultCameraZoom to avoid breaking camera bops. + * Tweens the camera zoom to the desired amount. */ - public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?directMode:Bool, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. cancelCameraZoomTween(); var targetZoom = zoom * FlxCamera.defaultZoom; - if (duration == 0) + if (directMode) // Direct mode: Tween defaultCameraZoom for basic "smooth" zooms. { - // Instant zoom. No tween needed. - defaultCameraZoom = targetZoom; + if (duration == 0) + { + // Instant zoom. No tween needed. + defaultCameraZoom = targetZoom; + } + else + { + // Zoom tween! Caching it so we can cancel/pause it later if needed. + cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease}); + } } - else + else // Additive mode: Tween additiveCameraZoom for ease-based zooms. { - // Zoom tween! Caching it so we can cancel/pause it later if needed. - cameraZoomTween = FlxTween.tween(this, {defaultCameraZoom: targetZoom}, duration, {ease: ease}); + if (duration == 0) + { + // Instant zoom. No tween needed. + additiveCameraZoom = targetZoom; + } + else + { + // Zoom tween! Caching it so we can cancel/pause it later if needed. + cameraZoomTween = FlxTween.tween(this, {additiveCameraZoom: targetZoom}, duration, {ease: ease}); + } } } diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index b9b634ffe..d1741a463 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -62,17 +62,23 @@ class ZoomCameraSongEvent extends SongEvent var zoom:Null = data.getFloat('zoom'); if (zoom == null) zoom = 1.0; + var duration:Null = data.getFloat('duration'); if (duration == null) duration = 4.0; + var mode:Null = data.getString('mode'); + if (mode == null) mode = 'additive'; + var ease:Null = data.getString('ease'); if (ease == null) ease = 'linear'; + var directMode:Bool = mode == 'direct'; + // If it's a string, check the value. switch (ease) { case 'INSTANT': - PlayState.instance.tweenCameraZoom(zoom, 0); + PlayState.instance.tweenCameraZoom(zoom, 0, directMode); default: var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; @@ -83,7 +89,7 @@ class ZoomCameraSongEvent extends SongEvent return; } - PlayState.instance.tweenCameraZoom(zoom, durSeconds, easeFunction); + PlayState.instance.tweenCameraZoom(zoom, durSeconds, directMode, easeFunction); } } @@ -96,8 +102,9 @@ class ZoomCameraSongEvent extends SongEvent * ``` * { * 'zoom': FLOAT, // Target zoom level. - * 'duration': FLOAT, // Optional duration in steps - * 'ease': ENUM, // Optional easing function + * 'duration': FLOAT, // Optional duration in steps. + * 'mode': ENUM, // Whether to set additive zoom or direct zoom. + * 'ease': ENUM, // Optional easing function. * } * @return SongEventSchema */ @@ -120,6 +127,13 @@ class ZoomCameraSongEvent extends SongEvent type: SongEventFieldType.FLOAT, units: 'steps' }, + { + name: 'mode', + title: 'Mode', + defaultValue: 'additive', + type: SongEventFieldType.ENUM, + keys: ['Additive' => 'additive', 'Direct' => 'direct'] + }, { name: 'ease', title: 'Easing Type', From 5e0de6d1ce9dcf1a9785b9b2b23b2e73dc008a9e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 21 Mar 2024 23:57:26 -0400 Subject: [PATCH 7/7] Fix some issues with events unintentionally sharing data after being edited via the toolbox. --- source/funkin/data/song/SongData.hx | 2 +- source/funkin/ui/debug/charting/ChartEditorState.hx | 9 +++++++++ .../debug/charting/commands/SetItemSelectionCommand.hx | 7 ++++++- .../charting/components/ChartEditorEventSprite.hx | 10 ++++++++-- .../charting/toolboxes/ChartEditorEventDataToolbox.hx | 7 ++++--- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 938859ff2..26380947a 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -706,7 +706,7 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR this = new SongEventDataRaw(time, eventKind, value); } - public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic + public function valueAsStruct(?defaultKey:String = "key"):Dynamic { if (this.value == null) return {}; if (Std.isOfType(this.value, Array)) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index c59a5abdb..bdc0d311e 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -878,6 +878,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var noteDisplayDirty:Bool = true; + var noteTooltipsDirty:Bool = true; + /** * Whether the selected charactesr have been modified and the health icons need to be updated. */ @@ -1541,6 +1543,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Make sure view is updated when the variation changes. noteDisplayDirty = true; notePreviewDirty = true; + noteTooltipsDirty = true; notePreviewViewportBoundsDirty = true; switchToCurrentInstrumental(); @@ -1562,6 +1565,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Make sure view is updated when the difficulty changes. noteDisplayDirty = true; notePreviewDirty = true; + noteTooltipsDirty = true; notePreviewViewportBoundsDirty = true; // Make sure the difficulty we selected is in the list of difficulties. @@ -3663,8 +3667,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState selectionSquare.width = eventSprite.width; selectionSquare.height = eventSprite.height; } + + // Additional cleanup on notes. + if (noteTooltipsDirty) eventSprite.updateTooltipText(); } + noteTooltipsDirty = false; + // Sort the notes DESCENDING. This keeps the sustain behind the associated note. renderedNotes.sort(FlxSort.byY, FlxSort.DESCENDING); // TODO: .group.insertionSort() diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 46fcca87c..73cf80fa0 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -51,7 +51,12 @@ class SetItemSelectionCommand implements ChartEditorCommand } var eventData = eventSelected.valueAsStruct(defaultKey); - state.eventDataToPlace = eventData; + var eventDataClone = Reflect.copy(eventData); + + if (eventDataClone != null) + { + state.eventDataToPlace = eventDataClone; + } state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index f680095d7..c996079bc 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -164,8 +164,7 @@ class ChartEditorEventSprite extends FlxSprite this.eventData = value; // Update the position to match the note data. updateEventPosition(); - // Update the tooltip text. - this.tooltip.tipData = {text: this.eventData.buildTooltip()}; + updateTooltipText(); return this.eventData; } } @@ -188,6 +187,13 @@ class ChartEditorEventSprite extends FlxSprite this.updateTooltipPosition(); } + public function updateTooltipText():Void + { + if (this.eventData == null) return; + if (this.isGhost) return; + this.tooltip.tipData = {text: this.eventData.buildTooltip()}; + } + public function updateTooltipPosition():Void { // No tooltip for ghost sprites. diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index 50b341272..f0949846d 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -258,14 +258,15 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox // Edit the event data of any existing events. if (!_initializing && chartEditorState.currentEventSelection.length > 0) { - for (event in chartEditorState.currentEventSelection) + for (songEvent in chartEditorState.currentEventSelection) { - event.eventKind = chartEditorState.eventKindToPlace; - event.value = chartEditorState.eventDataToPlace; + songEvent.eventKind = chartEditorState.eventKindToPlace; + songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace); } chartEditorState.saveDataDirty = true; chartEditorState.noteDisplayDirty = true; chartEditorState.notePreviewDirty = true; + chartEditorState.noteTooltipsDirty = true; } } }