Skip to main content
The skinning system allows complete visual and audio customization of the game. Users can create custom skins or download community-made skins to personalize their experience.

Skin Interface

The ISkin interface defines what a skin can provide:
public interface ISkin
{
    Drawable? GetDrawableComponent(ISkinComponentLookup lookup);
    Texture? GetTexture(string componentName);
    Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
    ISample? GetSample(ISampleInfo sampleInfo);
    IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup);
}

Skin Resources

Drawable UI elements and gameplay components:
Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
Examples:
  • Hit circles
  • Sliders
  • Score displays
  • Health bars
  • Combo counters
Image assets for various game elements:
Texture? GetTexture(string componentName)
Examples:
  • hitcircle.png
  • sliderb0.png
  • cursor.png
  • menu-background.jpg
Sound effects:
ISample? GetSample(ISampleInfo sampleInfo)
Examples:
  • Hit sounds (normal, whistle, finish, clap)
  • Menu sounds
  • Combo break sound
Skin-specific settings:
IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
Examples:
  • Colors (combo colors, menu colors)
  • Fonts
  • Layout preferences

Built-in Skins

osu! includes several official skins:

Default Skins

Modern default skin with clean visuals.
public class ArgonSkin : Skin
{
    public static SkinInfo CreateInfo() => new SkinInfo
    {
        ID = SkinInfo.ARGON_SKIN,
        Name = "osu! (argon)",
        Creator = "team osu!",
        Protected = true,
    };
}
Professional variant of Argon with refined aesthetics.
public class ArgonProSkin : ArgonSkin
{
    // Enhanced version of Argon
}
Geometric-themed skin.
ID = SkinInfo.TRIANGLES_SKIN
Traditional osu! skin (osu!stable style).
public class DefaultLegacySkin : LegacySkin
{
    ID = SkinInfo.CLASSIC_SKIN
}
Nostalgic retro-styled skin.
ID = SkinInfo.RETRO_SKIN

Skin Manager

The SkinManager handles skin operations:
public class SkinManager : ModelManager<SkinInfo>, ISkinSource, IModelImporter<SkinInfo>
{
    public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>();
    public readonly Bindable<Live<SkinInfo>> CurrentSkinInfo;
    public Skin DefaultClassicSkin { get; }
}

Current Skin Management

1

Set Current Skin

Change the active skin:
skinManager.CurrentSkinInfo.Value = skinInfo;
The CurrentSkin bindable updates automatically.
2

Get Current Skin

Access the current skin:
Skin currentSkin = skinManager.CurrentSkin.Value;
3

Listen for Changes

Subscribe to skin changes:
skinManager.CurrentSkin.ValueChanged += skin =>
{
    // Handle skin change
};

Listing and Selection

Getting All Skins

public IList<Live<SkinInfo>> GetAllUsableSkins()
{
    // Returns:
    // 1. Default skins (Argon, Triangles, Classic, etc.)
    // 2. Random skin option
    // 3. User skins (alphabetically)
}

Skin List Order

  1. Argon
  2. Argon Pro
  3. Triangles
  4. Classic
  5. Retro
  6. < Random Skin >
  7. User skins (A-Z)

Cycling Through Skins

skinManager.SelectNextSkin();
skinManager.SelectPreviousSkin();
skinManager.SelectRandomSkin();
Chooses a random skin, excluding the current one.

Importing Skins

Users can import custom skins:
// Import from file paths
await skinManager.Import("path/to/skin.osk");

// Import with progress tracking
await skinManager.Import(notification, tasks, parameters);

Supported Formats

  • .osk - osu! skin archive
  • Extracted skin folders
  • Legacy skin formats

Exporting Skins

Skins can be exported for sharing:
// Export current skin
await skinManager.ExportCurrentSkin();

// Export specific skin
await skinManager.ExportSkin(skinInfo);
Exported skins are packaged as .osk files for easy sharing.

Editing Skins

Making Skins Mutable

Protected skins (defaults) must be copied before editing:
public bool EnsureMutableSkin()
{
    // If current skin is protected, creates a copy
    // Returns true if a new skin was created
}
1

Check Protection

Protected skins cannot be directly modified:
if (skinInfo.Protected)
{
    // Need to create a copy
}
2

Create Copy

The system automatically creates a mutable copy:
skinInfo.Name = $"{originalName} (modified)";
3

Save Changes

Save modifications to the skin:
skinManager.Save(skin);

Saving Skins

public bool Save(Skin skin)
{
    // Serializes skin changes to JSON
    // Returns true if changes were made
}
Attempting to save an unmanaged skin throws an exception. Always call EnsureMutableSkin() first.

External Editing

Skins can be edited with external tools:
public Task<ExternalEditOperation<SkinInfo>> BeginExternalEditing(SkinInfo skin)
{
    // Exports skin to temporary location for editing
    // Watches for changes and re-imports when done
}

Skin Lookup System

The skin manager implements ISkinSource for resource lookups:
public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => 
    lookupWithFallback(s => s.GetDrawableComponent(lookup));

public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => 
    lookupWithFallback(s => s.GetTexture(componentName, wrapModeS, wrapModeT));

Fallback Chain

Resource Lookup Order

  1. Current user skin
  2. Default Classic skin (if current is legacy)
  3. Triangles skin (final fallback)
public IEnumerable<ISkin> AllSources
{
    get
    {
        yield return CurrentSkin.Value;
        
        if (CurrentSkin.Value is LegacySkin && CurrentSkin.Value != DefaultClassicSkin)
            yield return DefaultClassicSkin;
        
        if (CurrentSkin.Value != trianglesSkin)
            yield return trianglesSkin;
    }
}
This fallback system ensures that missing resources in user skins are filled in with defaults.

Skin Components

Skins can provide custom UI components:
public class TextElement : ISerialisableDrawable
{
    public string Text { get; set; }
    public FontUsage Font { get; set; }
}
public class PlayerName : FontAdjustableSkinComponent
{
    // Displays player username
}
public class BeatmapAttributeText : FontAdjustableSkinComponent
{
    // Displays beatmap info (title, artist, etc.)
}
public class ArgonJudgementCounter : ISkinComponent
{
    // Shows hit statistics during gameplay
}

Legacy Skin Support

The system supports legacy osu!stable skins:

Legacy Features

public class LegacyBeatmapSkin : LegacySkin
{
    // Loads skin.ini configuration
    // Supports classic hit sounds
    // Handles legacy sprite layouts
}

Legacy Elements

  • Combo Colors: From skin.ini
  • Hit Circle Overlays: Classic overlays
  • Legacy Fonts: Bitmap fonts
  • Classic Layouts: Original positioning

Skin Colors

Skins define global color schemes:
public class GlobalSkinColours
{
    public static readonly Color4 Blue = Color4Extensions.FromHex("#1188ee");
    public static readonly Color4 Pink = Color4Extensions.FromHex("#ee11dd");
    public static readonly Color4 Purple = Color4Extensions.FromHex("#aa11ee");
    // ... more colors
}

Beatmap Skins

Beatmaps can include their own skins:
public class BeatmapSkinProvidingContainer : SkinProvidingContainer
{
    // Provides beatmap-specific skin resources
    // Falls back to user skin if resource not found
}
Beatmap skins only override specific elements, allowing user skins to fill in the rest.

Skin Deletion

Remove unwanted skins:
// Delete with filter
skinManager.Delete(s => s.Name.Contains("old"));

// Delete specific skins
skinManager.Delete(new[] { skinInfo }, silent: false);
Protected skins (defaults) cannot be deleted. The filter automatically excludes them:
var items = realm.All<SkinInfo>()
    .Where(s => !s.Protected && !s.DeletePending);

Skin Renaming

Rename existing skins:
skinManager.Rename(skinInfo, "New Name");
This updates both the skin metadata and the skin.ini file.

Best Practices

Skinning Tips

  1. Always check for null when looking up skin resources
  2. Use fallback chain - don’t assume resources exist
  3. Test with multiple skins - ensure compatibility
  4. Respect user preferences - don’t force skin elements
  5. Handle legacy skins - support both modern and classic formats
  6. Cache lookups - skin resource retrieval can be expensive

Build docs developers (and LLMs) love