A fruit-catching game mode with horizontal movement and falling objects
osu!catch (formerly osu!ctb) is a game mode where players control a catcher at the bottom of the screen to catch falling fruits. The catcher moves horizontally to catch fruits that fall in time with the music.
The primary hit object - catch these for full points.Source:osu.Game.Rulesets.Catch/Objects/Fruit.cs:9
public class Fruit : PalpableCatchHitObject{ public override Judgement CreateJudgement() => new CatchJudgement(); public static FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4);}
Fruits cycle through 4 visual representations (apple, orange, grape, pear) based on their sequence position.
Fruits inherit from PalpableCatchHitObject, which includes properties for horizontal position and hyperdash mechanics.
Droplets: Regular ticks along the path (LargeTickHit/Miss)
Tiny Droplets: Small filler objects (SmallTickHit/Miss)
public class Droplet : PalpableCatchHitObject{ public override Judgement CreateJudgement() => new CatchDropletJudgement();}public class TinyDroplet : Droplet{ public override Judgement CreateJudgement() => new CatchTinyDropletJudgement();}
Bonus objects that spawn randomly across the screen.Source:osu.Game.Rulesets.Catch/Objects/BananaShower.cs:12
public class BananaShower : CatchHitObject, IHasDuration{ public override bool LastInCombo => true; public double Duration { get; set; } public override Judgement CreateJudgement() => new IgnoreJudgement();}
Banana Generation (BananaShower.cs:24-51):
private void createBananas(CancellationToken cancellationToken){ int startTime = (int)StartTime; int endTime = (int)EndTime; float spacing = (float)(EndTime - StartTime); while (spacing > 100) spacing /= 2; if (spacing <= 0) return; int count = 0; for (float time = startTime; time <= endTime; time += spacing) { cancellationToken.ThrowIfCancellationRequested(); AddNested(new Banana { StartTime = time, BananaIndex = count, Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(CreateHitSampleInfo()) } }); count++; }}
yield return new RulesetBeatmapAttribute("Approach Rate", @"AR", originalDifficulty.ApproachRate, effectiveDifficulty.ApproachRate, 10){ Description = "Affects how early fruits fade in on the screen.", AdditionalMetrics = [ new RulesetBeatmapAttribute.AdditionalMetric( "Fade-in time", LocalisableString.Interpolate( $@"{IBeatmapDifficultyInfo.DifficultyRangeInt( effectiveDifficulty.ApproachRate, CatchHitObject.PREEMPT_RANGE):#,0.##} ms" ) ) ]};
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]{ new KeyBinding(InputKey.Z, CatchAction.MoveLeft), new KeyBinding(InputKey.Left, CatchAction.MoveLeft), new KeyBinding(InputKey.X, CatchAction.MoveRight), new KeyBinding(InputKey.Right, CatchAction.MoveRight), new KeyBinding(InputKey.Shift, CatchAction.Dash), new KeyBinding(InputKey.MouseLeft, CatchAction.Dash),};