Mods (modifiers) change how a beatmap is played by altering gameplay mechanics, difficulty, visuals, or audio. They range from difficulty adjustments like Double Time and Hard Rock to fun effects like Barrel Roll.Every mod inherits from the Mod base class and implements specific interfaces to apply its effects at different stages of gameplay.
public enum ModType{ DifficultyReduction, // Makes the game easier (Easy, No Fail) DifficultyIncrease, // Makes the game harder (Hard Rock, Hidden, Double Time) Conversion, // Changes gameplay mechanics (Mirror, Random) Automation, // Autoplay and assists (Auto, Relax) Fun, // Visual/audio effects (Barrel Roll, Wiggle) System, // Internal system mods (Touch Device, Score V2)}
using osu.Framework.Graphics.Sprites;using osu.Framework.Localisation;using osu.Game.Rulesets.Mods;namespace osu.Game.Rulesets.MyRuleset.Mods{ public class MyRulesetModEasy : ModEasy { public override string Name => "Easy"; public override string Acronym => "EZ"; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Reduces overall difficulty"; public override double ScoreMultiplier => 0.5; }}
Extend built-in mod base classes like ModEasy, ModDoubleTime, or ModHidden when possible. They provide common functionality and ensure consistency.
public interface IApplicableToHitObject{ void ApplyToHitObject(HitObject hitObject);}
Example - Scale hit objects:
public class MyRulesetModBig : Mod, IApplicableToHitObject{ public override string Name => "Big"; public override string Acronym => "BG"; public override LocalisableString Description => "Everything is BIGGER"; public override double ScoreMultiplier => 1.0; public override ModType Type => ModType.Fun; public void ApplyToHitObject(HitObject hitObject) { // Scale up position values if (hitObject is IHasPosition positionedObject) { positionedObject.Position *= 1.5f; } }}
public interface IApplicableToRate{ BindableNumber<double> SpeedChange { get; }}
Example - Double Time:
using osu.Framework.Bindables;using osu.Framework.Audio;using osu.Game.Configuration;public abstract class ModDoubleTime : ModRateAdjust{ public override string Name => "Double Time"; public override string Acronym => "DT"; public override LocalisableString Description => "Zoooooooooom..."; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => SpeedChange.IsDefault; [SettingSource("Speed increase", "The actual increase to apply")] public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(1.5) { MinValue = 1.01, MaxValue = 2, Precision = 0.01, }; public override double ScoreMultiplier => 1.12;}
public interface IApplicableToDrawableRuleset<TObject> where TObject : HitObject{ void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset);}
Example - Barrel Roll:
public class MyRulesetModBarrelRoll : ModBarrelRoll<MyRulesetHitObject>{ // Playfield rotation is handled by base class public override string Description => "The whole playfield is on a wheel!";}
using System;using System.Linq;using osu.Game.Beatmaps;using osu.Game.Replays;using osu.Game.Rulesets.MyRuleset.Objects;using osu.Game.Rulesets.Replays;public class MyRulesetAutoGenerator : AutoGenerator<MyRulesetReplayFrame>{ public MyRulesetAutoGenerator(IBeatmap beatmap) : base(beatmap) { } public override Replay Generate() { var replay = new Replay(); foreach (var hitObject in Beatmap.HitObjects.Cast<MyRulesetHitObject>()) { // Add frame to press the button at the hit time replay.Frames.Add(new MyRulesetReplayFrame { Time = hitObject.StartTime, Position = hitObject.Position, Actions = { MyRulesetAction.Button1 }, }); // Release after a short time replay.Frames.Add(new MyRulesetReplayFrame { Time = hitObject.StartTime + 50, Position = hitObject.Position, }); } return replay; }}
Register all mods in your ruleset’s GetModsFor() method:
osu.Game.Rulesets.MyRuleset/MyRulesetRuleset.cs
public override IEnumerable<Mod> GetModsFor(ModType type){ switch (type) { case ModType.DifficultyReduction: return new Mod[] { new MyRulesetModEasy(), new MyRulesetModNoFail(), new MultiMod(new MyRulesetModHalfTime(), new MyRulesetModDaycore()), }; case ModType.DifficultyIncrease: return new Mod[] { new MyRulesetModHardRock(), new MultiMod(new MyRulesetModSuddenDeath(), new MyRulesetModPerfect()), new MultiMod(new MyRulesetModDoubleTime(), new MyRulesetModNightcore()), new MyRulesetModHidden(), new MyRulesetModFlashlight(), }; case ModType.Conversion: return new Mod[] { new MyRulesetModMirror(), new MyRulesetModRandom(), }; case ModType.Automation: return new Mod[] { new MultiMod(new MyRulesetModAutoplay(), new MyRulesetModCinema()), new MyRulesetModRelax(), }; case ModType.Fun: return new Mod[] { new MyRulesetModBarrelRoll(), new ModWindUp(), new ModWindDown(), }; case ModType.System: return new Mod[] { new ModTouchDevice(), new ModScoreV2(), }; default: return Array.Empty<Mod>(); }}
Use MultiMod to group related mods that serve similar purposes (like SuddenDeath/Perfect or DoubleTime/Nightcore).
public partial class TestSceneMods : OsuTestScene{ [Test] public void TestDoubleTime() { var ruleset = new MyRulesetRuleset(); var mod = ruleset.CreateMod<MyRulesetModDoubleTime>(); AddAssert("Speed is 1.5x", () => mod.SpeedChange.Value == 1.5); AddAssert("Score multiplier correct", () => mod.ScoreMultiplier == 1.12); }}
Extend Base Classes: Use built-in mod base classes (ModDoubleTime, ModHidden, etc.) for consistency and automatic updates.
Interface Order: Apply effects at the correct stage using appropriate interfaces. Order matters: IApplicableToHitObject → IApplicableToDifficulty → IApplicableToDrawableHitObject.
Score Multipliers: Use multipliers > 1.0 for difficulty increases, < 1.0 for reductions, and 1.0 for neutral mods. Follow osu!‘s conventions (DT = 1.12, HD = 1.06, etc.).
Ranked Status: Set Ranked = false for mods with custom settings or experimental features. Only default configurations should be ranked.