ppy / osu

rhythm is just a *click* away!
https://osu.ppy.sh
MIT License
15.53k stars 2.31k forks source link

Allow keyboard hitobject placement while song is playing (live mapping) #6608

Open ghost opened 5 years ago

ghost commented 5 years ago

In addition to the current beatmap editor, allow beatmap creators to play the song over a blank beatmap, and when they click it puts a circle there, and when they click and drag it puts a slider there.

I think this could be a much more fun and easy way to create beatmaps, definitely in the beginning.

Maybe once all the sliders and circles are put using this method, their positions can be fine-tuned (and combos can be chosen etc.) in the traditional editor.

Also, alternatively/additionally, combos etc. and other features could be handled via key binds in this "fast" editor.


Basic features I think would be useful:

TsavyPrince commented 5 years ago

I support this.

peppy commented 5 years ago

This is called live mapping. I believe it is supported in stable and will also come to lazer eventually.

enoslayd commented 5 years ago

Will this be standard only?

peppy commented 5 years ago

Editor features will be present in all bundled rulesets.

ViRiXDreamcore commented 1 year ago

Has this been added to Lazer yet? Was trying to map a song and wanted to see if it made it in before I opened a request for it.

D-Maxwell commented 10 months ago

Has this been added to Lazer yet? Was trying to map a song and wanted to see if it made it in before I opened a request for it.

Doesn't seem like it.

ghost commented 9 months ago

IMO this is more important than any other type of editor. If osu! only had one way to create maps, it should be live mapping.

peppy commented 9 months ago

You can basically do this now? Have you tried?

snalgae commented 4 months ago

I think this should be closed, live mapping is possible.

https://github.com/ppy/osu/assets/104220939/b878b15e-2e9f-4f73-8905-f3e33bae68c4

Joehuu commented 4 months ago

The title is kind of misleading. "Live mapping" in stable uses keyboard bindings (instead of mouse) to place circles (ref: https://osu.ppy.sh/wiki/en/Client/Beatmap_editor/Menu#compose). Stable also doesn't support slider placement, which OP also wanted and can be exclusive to lazer.

TL;DR (or in a different way to say it):

bdach commented 4 months ago

Tried to do a barebones implementation of this for taiko and hit a few walls.

diff --git a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs
index 217bb8139c..f206130dd2 100644
--- a/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/DrawableTaikoEditorRuleset.cs
@@ -2,6 +2,7 @@
 // See the LICENCE file in the repository root for full licence text.

 using System.Collections.Generic;
+using osu.Framework.Allocation;
 using osu.Framework.Bindables;
 using osu.Game.Beatmaps;
 using osu.Game.Configuration;
@@ -20,6 +21,12 @@ public DrawableTaikoEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyLi
         {
         }

+        [BackgroundDependencyLoader]
+        private void load()
+        {
+            KeyBindingInputManager.Add(new TaikoLiveMapper());
+        }
+
         protected override void LoadComplete()
         {
             base.LoadComplete();
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoLiveMapper.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoLiveMapper.cs
new file mode 100644
index 0000000000..c3a0092695
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoLiveMapper.cs
@@ -0,0 +1,68 @@
+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+    public partial class TaikoLiveMapper : Component, IKeyBindingHandler<TaikoAction>
+    {
+        [Resolved]
+        private EditorBeatmap editorBeatmap { get; set; } = null!;
+
+        [Resolved]
+        private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
+
+        [Resolved]
+        private EditorClock editorClock { get; set; } = null!;
+
+        public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
+        {
+            if (e.Repeat)
+                return false;
+
+            double quantisedTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
+
+            var existing = editorBeatmap.HitObjects.FirstOrDefault(h => h.StartTime == quantisedTime);
+
+            switch (existing)
+            {
+                case Hit hit:
+                {
+                    if ((hit.Type == HitType.Centre && (e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre)) ||
+                        (hit.Type == HitType.Rim && (e.Action == TaikoAction.LeftRim || e.Action == TaikoAction.RightRim)))
+                    {
+                        hit.IsStrong = true;
+                    }
+
+                    return true;
+                }
+
+                case null:
+                {
+                    editorBeatmap.Add(new Hit
+                    {
+                        Type = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre ? HitType.Centre : HitType.Rim,
+                        StartTime = quantisedTime
+                    });
+
+                    return true;
+                }
+
+                default:
+                    return false;
+            }
+        }
+
+        public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
+        {
+        }
+    }
+}
diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs
index 174b278d89..04fcad4262 100644
--- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs
+++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs
@@ -68,7 +68,7 @@ protected override void LoadComplete()
         private void regenerateAutoplay()
         {
             var autoplayMod = drawableRuleset.Mods.OfType<ModAutoplay>().Single();
-            drawableRuleset.SetReplayScore(autoplayMod.CreateScoreFromReplayData(drawableRuleset.Beatmap, drawableRuleset.Mods));
+            //drawableRuleset.SetReplayScore(autoplayMod.CreateScoreFromReplayData(drawableRuleset.Beatmap, drawableRuleset.Mods));
         }

         private void addHitObject(HitObject hitObject)
@@ -85,7 +85,7 @@ private void removeHitObject(HitObject hitObject)

         public override bool PropagatePositionalInputSubTree => false;

-        public override bool PropagateNonPositionalInputSubTree => false;
+        //public override bool PropagateNonPositionalInputSubTree => false;

         public PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer();

The play here is probably to make instantiating the ruleset input manager easier. I can't imagine trying to jam the live mapper in the only current viable place is going to be nice or easy.