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.
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
Set Current Skin
Change the active skin: skinManager . CurrentSkinInfo . Value = skinInfo ;
The CurrentSkin bindable updates automatically.
Get Current Skin
Access the current skin: Skin currentSkin = skinManager . CurrentSkin . Value ;
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
Argon
Argon Pro
Triangles
Classic
Retro
< Random Skin >
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
}
Check Protection
Protected skins cannot be directly modified: if ( skinInfo . Protected )
{
// Need to create a copy
}
Create Copy
The system automatically creates a mutable copy: skinInfo . Name = $" { originalName } (modified)" ;
Save Changes
Save modifications to the 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
Current user skin
Default Classic skin (if current is legacy)
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
Always check for null when looking up skin resources
Use fallback chain - don’t assume resources exist
Test with multiple skins - ensure compatibility
Respect user preferences - don’t force skin elements
Handle legacy skins - support both modern and classic formats
Cache lookups - skin resource retrieval can be expensive